为什么不是这种情况下调用的最合适的构造函数?

时间:2016-05-21 09:38:26

标签: c++ constructor type-conversion copy-constructor enable-if

考虑以下课程:

class foo {
    int data;
public:
    template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
    foo(const T& i) : data{ i } { cout << "Value copy ctor" << endl; }

    template <typename T, typename = enable_if_t<is_constructible<int, T>::value>>
    foo(T&& i) : data{ i } { cout << "Value move ctor" << endl; }

    foo(const foo& other) : data{ other.data } { cout << "Copy ctor" << endl; }

    foo(foo&& other) : data{ other.data } { cout << "Move ctor" << endl; }

    operator int() { cout << "Operator int()" << endl; return data; }
};

当然,通过任何类型的引用来获取单个int都没有多大意义,但这只是一个例子。复制data成员可能非常昂贵,因此所有移动语义都是如此。

这个花哨的模板基本上可以启用任何可以构造data的类型。因此,可以通过复制或移动满足此条件的任何类型的值,或者仅通过复制或移动foo类型的另一个对象来构造foo对象。到目前为止非常直接。

当您尝试执行以下操作时会出现问题:

foo obj1(42);
foo obj2(obj1);

这个应该做什么(至少在我看来)是通过将值42移入其中来构造第一个对象(因为它是一个右值),然后构造第二个对象通过复制第一个对象来对象。所以这应该打印出来的是:

Value move ctor
Copy ctor

但实际打印出来的是:

Value move ctor
Operator int
Value move ctor

第一个对象构造得很好,没问题。但是,程序不是调用复制构造函数来构造第二个对象,而是将第一个对象转换为另一个类型(通过我们定义的转换),然后调用foo的另一个构造函数,它可以从该类型移动(因为它&# 39;那时是一个右值。)

我觉得这很奇怪,而且这绝对不是我想从这段代码中得到的行为。我认为在构造第二个对象时调用复制构造函数更有意义,因为考虑到我提供的参数类型,这似乎更简单。

任何人都可以解释这里发生的事情吗?当然我理解,因为用户定义的转换为int,这是一个非常有效的路径,但我无法理解它。为什么编译器拒绝简单地调用与提供的值具有完全相同的参数类型的构造函数?这不是最琐碎的事情,因此默认行为?调用转换运算符也会执行副本,所以我不认为这比简单地调用复制构造函数更快或更优。

2 个答案:

答案 0 :(得分:5)

您的模板&#34;移动&#34;构造函数(T = foo &)的参数类型为foo &,这是一个比复制构造函数更好的匹配,因为它只需要const foo &。然后,您的模板构造函数通过将data转换为i并调用int来填充operator int()

最简单的即时修复可能是使用enable_if限制您的移动构造函数移动操作:如果T被推断为左值引用类型(意味着您的T&& i会崩溃为也是左值参考),强制替换失败。

template <typename T, typename = enable_if_t<is_constructible<int, T>::value>,
                      typename = enable_if_t<!is_lvalue_reference<T>::value>>
foo(T&& i) : data( std::move(i) ) { cout << "Value move ctor" << endl; }

注意:由于您的参数在构造函数中有一个名称,就像所有其他命名对象一样,它是一个左值。鉴于您想要从中移动,您可以使用std::move

更一般地说,您可以使用完美转发(接受左值和右值),并仅将foo本身作为特殊例外删除:

template <typename T, typename = enable_if_t<is_constructible<int, T>::value>
                    , typename = enable_if_t<!is_same<decay_t<T>, foo>::value>
foo(T&& i) : data( std::forward<T>(i) ) { cout << "Forwarding ctor" << endl; }

这将替换您的值副本值移动构造函数。

另一个注意事项:is_constructible<int, T>::value是一个测试,可以告诉您data(std::forward<T>(i))是否格式正确。它测试data{std::forward<T>(i)}是否格式正确。结果不同的Tlong,因为longint转化是缩小转化,{}不允许缩小转化次数

答案 1 :(得分:0)

这是因为operator int()

因为operator int()obj1调用operator int()并将自己投射为int

可能的解决方案:

  1. 使用static_cast<foo>。这会将通常应该转换为int的变量转换为foo

  2. 任何人请编辑此问题并填写此内容。我没有更多的想法。