Search code examples
c++forward-declarationdefault-copy-constructor

implicit copying of a struct with declard and argument-forwarding constructors


I would like to have a struct initialize its member with forwarding arguments. This compiles and works fine except when I declare a destructor and when I try to return the struct from a function (which I believe necessitates a copy constructor).

#include <utility>

struct Foo 
{
  int val;

  Foo(int val) : val(val)
  {
  }
};

struct FooContainer
{
    Foo member;

    template<typename... Args>
    FooContainer(Args&&... args) : 
      member(std::forward<Args>(args)...)
    {}

    ~FooContainer() {}
};

FooContainer getFooContainer()
{
  FooContainer retval(0);
  return retval;
}

int main() {}

The compiler error is:

example.cc: In constructor ‘FooContainer::FooContainer(Args&& ...) [with Args = FooContainer&]’:
example.cc:27:   instantiated from here
example.cc:18: error: no matching function for call to ‘Foo::Foo(FooContainer&)’
example.cc:7: note: candidates are: Foo::Foo(int)
example.cc:4: note:                 Foo::Foo(const Foo&)

It looks like it's trying to generate a copy constructor for FooContainer but fails because it doesn't have a way to initialize Foo. Yet if I remove the FooContainer constructor or destructor, it compiles fine.* Why does it do this?

*on http://cpp.sh/ with GCC 4.9.2 anyway. g++ 4.4.3 on Ubuntu gives the same error even if the destructor isn't declared.


Solution

  • I can't tell you exactly why this happens (a standard-expert will be able to) but the problem is actually caused because you have defined a user-defined destructor.

    Remove that and the problem goes away (you want to use the rule-of-zero anyway, right?)

    If you must have the destructor and can't refactor it away for some reason, then replacing the move-constructor (which you implicitly deleted by providing a destructor) will also solve it.

    Solution 1 - use rule of 0:

    #include <utility>
    
    struct Foo
    {
        int val;
    
        Foo(int val) : val(val)
        {
        }
    };
    
    struct FooContainer
    {
        Foo member;
    
        template<typename... Args>
        FooContainer(Args&&... args) :
        member(std::forward<Args>(args)...)
        {}
    
    //    ~FooContainer() {}
    };
    
    FooContainer getFooContainer()
    {
        FooContainer retval(0);
        return retval;
    }
    
    int main() {}
    

    Solution 2 - use rule of 5:

    #include <utility>
    
    struct Foo
    {
        int val;
    
        Foo(int val) : val(val)
        {
        }
    };
    
    struct FooContainer
    {
        Foo member;
    
        template<typename... Args>
        FooContainer(Args&&... args) :
        member(std::forward<Args>(args)...)
        {}
    
        FooContainer(const FooContainer&) = default;
        FooContainer(FooContainer&&) = default;
        FooContainer& operator=(const FooContainer&) = default;
        FooContainer& operator=(FooContainer&&) = default;
    
        ~FooContainer() {}
    };
    
    FooContainer getFooContainer()
    {
        FooContainer retval(0);
        return retval;
    }
    
    int main() {}