c – 当参数是引用时,可变参数模板构造函数选择失败

我有以下代码:

#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;
}
最佳答案

Why is this?

如果有以下声明:

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

电话:

A{3};

将O类型推断为int,因此最终得到以下实例:

A(int& o);

但是你在做什么,是你试图将一个右值(当然是3)绑定到这个实例化的非const左值引用,这是不允许的.

Is it possible to fix it and avoid copies?

您也可以将o类型声明为转发引用,然后将其转发给x的构造函数(但对于像int这样的基本类型,这根本不是必需的):

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

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

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

Using a universal reference fixes the problem, but a const reference (which is actually what I wanted) does not, unfortunately. In addition, even saving the input parameter into a separate value (avoiding an rvalue) will still not work.

这是因为通用引用几乎总是产生精确匹配,并且采用通用引用的第一个构造函数是重载解析过程中最可行的函数.

通过右值时,推导出的&&对于rvalues而言,它比const int&更好地匹配.

通过左值时,推导出的int&对于非常量左值(比如变量x),它比const int&更好地匹配.

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

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

转载注明原文:c – 当参数是引用时,可变参数模板构造函数选择失败 - 代码日志