Search code examples
c++constructorvariadic-templatesperfect-forwarding

Does substitution failure block special member function generation?


I am trying to understand at a non-superficial level why the following code does not compile:

#include <vector>

template<typename T>
struct wrapper {
    T wrapped_value;

    wrapper() {}

    template<typename... Args>
    wrapper(Args&&... args) : wrapped_value( std::forward<Args>(args)... ) {
    }

};

struct A {
    int a;
    A(int i = 0) : a(i) {
    }
};

int main() {

    std::vector<wrapper<A>> v1;
    std::vector<wrapper<A>> v2;
    v1 = v2;

}

I can tell from the error message in the std::vector implementation that the above is failing because the perfect forwarding constructor of wrapper<T> matches the copy constructor. The copy constructor created by substitution into the constructor template would be

    wrapper(wrapper<A>& w) : wrapped_value( w ) {
    }

Because wrapped_value is of type A this is an error since A does not have a constructor accepting a wrapper<A>.

But isn't "substitution failure not an error"? So the constructor template fails when the compiler attempts to use it as a copy constructor -- why does this block the automatic generation of a copy constructor? Or does it not and the real problem has something to do with the implementation of std::vector?

Also, this is a toy example but what is the best way around this sort of thing in my real code when dealing with classes like this?

  1. Use "pass-by-value-then-move" rather than perfect forwarding?
  2. Just define the copy constructor as default?
  3. Use an std::in_place_t parameter before the variadic parametes in the perfect forwarding constructor?
  4. Disable the constructor template in the case of copy construction via enable_if et. al.

Solution

  • Substitution is not failing and special function generation is not being blocked. Template substitution leads to a constructor that is a better match than the compiler-generated copy constructor so it is selected which causes a syntax error.

    Let's simplify the problem illustrated in the question by getting rid of usage of std::vector. The following will also fail to compile:

    #include <utility>
    
    template<typename T>
    struct wrapper {
        T wrapped_value;
    
        wrapper() {}
    
        template<typename... Args>
        wrapper(Args&&... args) : wrapped_value(std::forward<Args>(args)...) {
        }
    
    };
    
    struct A {
        int a;
        A(int i = 0) : a(i) {
        }
    };
    
    int main() {
    
        wrapper<A> v1;
        wrapper<A> v2(v1);
    
    }
    

    In the above template substitution is not failing as applied to the required the copy constructor. We end up with two overloads of the copy constructor, one generated by the compiler as part of special function generation and one produced by substituting the type of v1 into the constructor template:

    wrapper(wrapper<A>& rhs); // (1) instantiated from the perfect forwarding template
    
    wrapper(const wrapper<A>& rhs); // (2) compiler-generated ctor.
    

    by the rules of C++ (1) has to be chosen since v1 in the orginal code is not const. You can actually check this by making it const and the program will no longer fail to compile.

    As for what to do about this, as @jcai mentions in comments, Scott Meyers' Item 27 in Effective Modern C++ is about how to handle this issue -- basically it comes down to either not using perfect forwarding or using "tag dispatch" -- so I will not go into it here.