4

我有以下代码:

#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;
}
4

1 回答 1

8

为什么是这样?

在以下声明的情况下:

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

电话:

A{3};

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

A(int& o);

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

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

您也可以将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 引用(这实际上是我想要的)没有。此外,即使将输入参数保存为单独的值(避免右值)仍然不起作用。

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

当传递一个右值时,推导的右值比 rvalueint&&更好const int&

传递左值时,推导int&的非 const 左值(如您的变量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;
};

演示

于 2014-09-23T17:38:16.023 回答