Search code examples
c++c++11templatesvariadic-templates

Specialization of variadic constructor template of class template


Here's a class with variadic constructor and it's specializations for copy and move from a temporary.

template<class Obj>
class wrapper {
protected:
   Obj _Data;
public:

   wrapper(const wrapper<Obj>& w): _Data(w._Data) {}

   wrapper(wrapper<Obj>&& w):
      _Data(std::forward<Obj>(w._Data)) {}

   template<class ...Args>
   wrapper(Args&&... args):
      _Data(std::forward<Args>(args)...) {}

   inline Obj& operator()() { return _Data; }

   virtual ~wrapper() {}
};

When I use one of specializations like this

wrapper<int> w1(9);
wrapper<int> w2(w1);

I'm getting the error: type of w1 is deduced as int.

Output from VS2012:

error C2440: 'initializing' : cannot convert from 'win::util::wrapper<int>' to 'int'

How to solve this problem?


Solution

  • You're getting bitten by the greedy perfect forwarding constructor.

    wrapper<int> w2(w1);
    

    In the line above, the perfecting forwarding constructor is a better match as compared to the copy constructor, because Args is deduced as wrapper<int>&.

    A quick fix solution is to change the line above to

    wrapper<int> w2(static_cast<wrapper<int> const&>(w1));
    

    this correctly calls the copy constructor but, besides being unnecessarily verbose, doesn't solve the basic problem.

    To solve the original problem, you need to conditionally disable the perfect forwarding constructor when Args is the same as wrapper<Obj>.

    Here's an excellent blog post describing the problem, and how to solve it. To summarize, you need to change the perfect forwarding constructor definition to

    template <typename... Args,
              DisableIf<is_related<wrapper<Obj>, Args...>::value>...>
    wrapper(Args&&... args):
        _Data(std::forward<Args>(args)...) {}
    

    where is_related is defined as

    template <typename T, typename... U>
    struct is_related : std::false_type {};
    
    template <typename T, typename U>
    struct is_related<T, U> : std::is_same<Bare<T>, Bare<U>> {};
    

    and Bare is

    template <typename T>
    using Bare = RemoveCv<RemoveReference<T>>;
    

    RemoveCv and RemoveReference are alias templates for std::remove_cv and std::remove_reference respectively.

    Live demo