【问题标题】:Variadic template constructor selection fails when argument is a reference当参数是引用时,可变参数模板构造函数选择失败
【发布时间】:2014-09-23 17:30:12
【问题描述】:

我有以下代码:

#include <iostream>
#include <typeinfo>

template <typename T>
struct A : T {
    template <typename ...Args>
    A(Args&&... params) : T(std::forward<Args>(params)...), x(0) {
        std::cout << "Member 'x' was default constructed\n"; 
    }

    template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
    A(O o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
        std::cout << "Member 'x' was constructed from arguments\n"; 
    }

    int x;
};

struct B{
    B(const char*) {}
};

int main() {
    A<B> a("test");
    A<B> y(3, "test");

    return 0;
}

效果很好,可以打印

Member 'x' was default constructed
Member 'x' was constructed from arguments

但是,如果第二个重载的第一个参数是一个引用,那么突然第二个重载永远不会被采用,并且编译失败:

template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
    A(O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
        std::cout << "Member 'x' was constructed from arguments\n"; 
    } // Note the O& in the arguments

这是为什么?是否可以修复它并避免复制?

编辑:使用通用引用显然可以让它再次工作。 const 引用,这是我真正想要的,也不起作用。

此外,即使将输入参数保存为单独的值(避免右值)仍然不起作用:

int main() {
    double x = 3.0;
    A<B> y(x, "test"); // Still not working

    return 0;
}

【问题讨论】:

    标签: c++ c++11 overloading variadic-templates universal-reference


    【解决方案1】:

    这是为什么?

    以下声明的情况:

    template <typename O>
    A(O& o);
    

    电话:

    A{3};
    

    O 类型推导出为int,因此您最终得到以下实例化:

    A(int& o);
    

    但是你在做什么,是你试图将一个 rvalue3 当然是)绑定到这个实例化的非常量左值引用,这是 不允许。

    是否可以修复它并避免复制?

    您也可以将o 类型声明为转发 引用,然后将forward 声明给x 的构造函数(但对于int 这样的原始类型,这是真的完全没有必要):

    template <typename O>
    A(O&& o) : x{std::forward<O>(o)} {}
    

    或者,您可以将构造函数声明为采用 const 左值引用(以便右值可以被它绑定):

    template <typename O>
    A(const O& o) : x{o} {}
    

    不幸的是,使用通用引用可以解决问题,但 const 引用(这实际上是我想要的)不能。此外,即使将输入参数保存为单独的值(避免右值)仍然不起作用。

    这是因为通用引用几乎总是产生完全匹配,并且第一个采用通用引用的构造函数是重载决策过程中最好的可行函数。

    当传递一个右值时,推导的int&amp;&amp;const int&amp; 更适合右值。

    当传递一个左值时,推导的int&amp;const int&amp; 更适合非常量左值(比如你的变量x)。

    话虽如此,这个采用通用引用的 greedy 构造函数在这两种情况下都是最好的可行函数,因为在实例化时:

    template <typename... Args>
    A(Args&&... params);
    
    template <typename O, typename... Args>
    A(const O& z, Args&&... params);
    

    例如用于以下调用:

    double x = 3.0;
    A a(x, "test");
    

    编译器最终得到:

    A(double&, const char (&)[5]);
    
    A(const double&, const char (&)[5]);
    

    第一个签名是更好的匹配(无需添加const 限定符)。

    如果出于某些原因你真的想要这个 O 类型被模板化(现在不管这是一个通用引用还是一个 const 左值引用),你必须禁用重载决议过程中的第一个贪婪构造函数,如果它的第一个参数可用于构造int(就像在这种情况下启用第二个):

    template <typename T>
    struct A : T
    {
        template <typename Arg, typename... Args, typename = typename std::enable_if<!std::is_constructible<int, Arg>::value>::type>
        A(Arg&& param, Args&&... params) : T(std::forward<Arg>(param), std::forward<Args>(params)...), x(0) {
            std::cout << "Member 'x' was default constructed\n"; 
        }
    
        template <typename O, typename... Args, typename = typename std::enable_if<std::is_constructible<int, O>::value>::type>
        A(const O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
            std::cout << "Member 'x' was constructed from arguments\n"; 
        }
    
        int x;
    };
    

    DEMO

    【讨论】:

    • 使用通用引用解决了这个问题,但不幸的是,const 引用(这实际上是我想要的)不能。 ideone.com/cU0pRQ 作为证明
    • @Svalorzen:现在怎么样?
    • @Svalorzen:但老实说,我不明白为什么你希望第一个参数是 const O&amp; o 而不是简单的 int o
    • 因为例如它可能是一个非常大的重型类型,具有operator int。或者,也许我想使用双精度数或无符号数,等等。我不觉得你的解决方案有效,因为那样你就需要枚举所有你不想要的可能类型,而你真的做不到。
    • @Svalorzen “我觉得你的解决方案不起作用”,哪个解决方案?提出的一个还是 cmets 中提出的一个?
    猜你喜欢
    • 1970-01-01
    • 2018-05-18
    • 2017-09-27
    • 2017-10-01
    • 2016-01-02
    • 2016-09-02
    • 2018-02-03
    • 2015-05-06
    • 2019-01-29
    相关资源
    最近更新 更多