问题
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