类模板的可变构造函数模板的专业化

时间:2014-01-27 20:38:44

标签: c++ c++11 variadic-templates

这是一个具有可变构造函数的类,它是用于复制和从临时移动的特殊化。

    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'

如何解决这个问题?

3 个答案:

答案 0 :(得分:6)

你被贪婪的完美转发构造函数所困扰。

wrapper<int> w2(w1);

在上面一行中,与复制构造函数相比,完善转发构造函数更匹配,因为Args被推断为wrapper<int>&

快速解决方案是将上面的行更改为

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

这正确地调用了复制构造函数,但除了不必要的冗长之外,还没有解决基本问题。

要解决原始问题,当Argswrapper<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>>;

RemoveCvRemoveReference分别是std::remove_cvstd::remove_reference的别名模板。

Live demo

答案 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&&)