Specialization of variadic constructor template of class template

試著忘記壹切 提交于 2020-01-02 05:19:46

问题


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?


回答1:


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




回答2:


The compiler is instantiating the constructor template on this line:

wrapper<int> w2(w1);

because the type of w1 is wrapper<int>& and overload resolution rules dicate that an exact match is preferable to a conversion. The constructor which takes a const wrapper<Obj>& requires a const qualification, while wrapper<Obj>&& is an rvalue-reference which can't bind to lvalues.

Normally, non-template overloads are a preferred target than template ones (thus in a normal situation the copy-constructor would be chosen), but because the constructor template takes a universal reference, it can deduce the type as an int making a perfect match and is therefore chosen, hence the error when the argument is forwarded.

As a fix, you can disable the perfect forwarding constructor through SFINAE in certain contexts as described in this article and by @Praetorian's answer.




回答3:


For me, using a more granular version of Praetorian's example worked for me. I defined something like is_compat<T, Arg>, and then feed this into an std::enable_if<> expression (leveraging std::decay<> to simplify matching).

EDIT: Found std::is_convertible, MUCH simpler.

Self-Contained Example:

  • tpl_spec_greedy.cc - Source file (also uses name_trait.h
  • tpl_spec_greedy.output.txt

Inline Example:

#include <type_traits>
// Syntactic sugar
using std::enable_if;
using std::is_convertible;
template<bool Expr, typename Result = void>
using enable_if_t = typename enable_if<Expr, Result>::type;
template<typename From, typename To>
using enable_if_convertible_t = enable_if_t<is_convertible<From, To>::value>;

Then you can do overloads like:

template<typename ... Args>
void my_func(Args&& ... args) {
    cout << "1. my_func<Args...>(" << name_trait_list<Args&&...>::join() << ")" << endl;
}

// Use template with enable_if to catch as many types as possible
template<typename T1,
    typename = enable_if_convertible_t<T1, string>>
void my_func(int y, T1&& z) {
    cout
        << "2. my_func<T1:string>(int, " << name_trait<decltype(z)>::name()
        << ")" << endl;
}

// Example using multiple types (let compiler handle the combinatorics)
template<typename T1, typename T2,
    typename = enable_if_t<is_convertible<T1, string>::value &&
                           is_convertible<T2, double>::value>>
void my_func(int y, T1&& z, T2&& zz) {
    cout
        << "3. my_func<T1:string, T2:double>(int, "
        << name_trait<decltype(z)>::name() << ", "
        << name_trait<decltype(zz)>::name() << ")" << endl;
}

(NOTE: name_trait* is a home-baked class)

Example output:

>>> ( my_func(1, 2, 5, string("!!!")) );
1. my_func<Args...>(int&&, int&&, int&&, std::string&&)

>>> ( my_func(3, string("Hello")) );
2. my_func<T1:string>(int, std::string&&)

>>> ( my_func(4, (const string&)string("kinda")) );
2. my_func<T1:string>(int, const std::string&)

>>> ( my_func(5, "World") );
2. my_func<T1:string>(int, const char[6]&)

>>> ( my_func(6, var) );
2. my_func<T1:string>(int, char[6]&)

>>> ( my_func(7, var, 12) );
3. my_func<T1:string, T2:double>(int, char[6]&, int&&)

>>> ( my_func(9, var, 12.0) );
3. my_func<T1:string, T2:double>(int, char[6]&, double&&)


来源:https://stackoverflow.com/questions/21391184/specialization-of-variadic-constructor-template-of-class-template

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!