这是一个具有可变构造函数的类,它是用于复制和从临时移动的特殊化。
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() {}
};
当我使用像这样的专业化之一时
wrapper<int> w1(9);
wrapper<int> w2(w1);
我收到错误:w1的类型推断为int
。
VS2012的输出:
error C2440: 'initializing' : cannot convert from 'win::util::wrapper<int>' to 'int'
如何解决这个问题?
答案 0 :(得分:6)
你被贪婪的完美转发构造函数所困扰。
wrapper<int> w2(w1);
在上面一行中,与复制构造函数相比,完善转发构造函数更匹配,因为Args
被推断为wrapper<int>&
。
快速解决方案是将上面的行更改为
wrapper<int> w2(static_cast<wrapper<int> const&>(w1));
这正确地调用了复制构造函数,但除了不必要的冗长之外,还没有解决基本问题。
要解决原始问题,当Args
与wrapper<Obj>
相同时,您需要有条件地禁用完美转发构造函数。
Here's一篇很好的博客文章,描述了这个问题,以及如何解决它。总而言之,您需要将完美的转发构造函数定义更改为
template <typename... Args,
DisableIf<is_related<wrapper<Obj>, Args...>::value>...>
wrapper(Args&&... args):
_Data(std::forward<Args>(args)...) {}
其中is_related
定义为
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>> {};
和Bare
是
template <typename T>
using Bare = RemoveCv<RemoveReference<T>>;
RemoveCv
和RemoveReference
分别是std::remove_cv
和std::remove_reference
的别名模板。
答案 1 :(得分:3)
编译器在此行上实例化构造函数模板:
wrapper<int> w2(w1);
因为w1
的类型为wrapper<int>&
,并且重载解析规则指出完全匹配比转换更可取。采用const wrapper<Obj>&
的构造函数需要const
限定,而wrapper<Obj>&&
是rvalue-reference,不能绑定到左值。
通常,非模板重载是首选目标而不是模板目标(因此在正常情况下会选择复制构造函数),但由于构造函数模板采用universal reference,因此它可以将类型推导为int
进行完美匹配并因此被选中,因此在转发参数时会出现错误。
作为修复,您可以在某些情况下通过SFINAE禁用完美的转发构造函数,如this article和@Praetorian's answer中所述。
答案 2 :(得分:1)
对我来说,使用更细粒度的Praetorian的例子对我有用。 我定义了类似 is_compat<T, Arg>
的内容,然后将其提供给std::enable_if<>
表达式(利用std::decay<>
来简化匹配)。
编辑:找到std::is_convertible
,更简单。
自包含示例:
内联示例:
#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>;
然后你可以做过载:
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;
}
(注意:name_trait*
是家庭烘焙课程)
示例输出:
>>> ( 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&&)