存在完美的转发构造函数时,按左值引用转发构造函数的目的是什么?
What's the intention of forward-by-lvalue-reference constructor while a perfect forwarding constructor exists?
我们以std::pair<T1, T2>
为例。它有以下两个构造函数:
constexpr pair( const T1& x, const T2& y ); // #1
template< class U1, class U2 > constexpr pair( U1&& x, U2&& y ); // #2
似乎#2 可以处理#1 可以处理的所有情况(没有更差的性能),除了参数是列表初始化器的情况。例如,
std::pair<int, int> p({0}, {0}); // ill-formed without #1
所以我的问题是:
如果#1 仅用于列表初始化器参数,因为 x
和 y
最终绑定到从列表初始化器初始化的临时对象,为什么不使用 constexpr pair( T1&& x, T2&& y );
代替?
不然#1的真实意图是什么?
您回答了自己的问题。
pair( const T1& x, const T2& y );
在 之前 在 C++17 中添加了转发构造函数。 constexpr
是在 C++14 中添加的。现在为什么要保留构造函数?两个原因:1) 向后兼容性和 2) 因为正如您所说,格式完美的代码将拒绝编译。 C++ 非常强调向后兼容性,并采用非常保守的方法来弃用代码,因为它可能会以意想不到的方式中断。
现在,如果您想要一个更真实的示例,请尝试 std::for_each
。您可以将其更改为完美转发函子,而不会破坏代码。但是,添加此更改的动力很小,因为它被指定只制作一个副本。换句话说,更改的优先级不是很高。
如果你要存放的物品是临时物品,但不可移动怎么办?
#include <type_traits>
#include <utility>
#include <iostream>
class test
{
public:
test() { std::cout << "ctor" << std::endl; }
test(const test&) { std::cout << "copy ctor" << std::endl; }
test(test&&) = delete; // { std::cout << "move ctor" << std::endl; }
~test() { std::cout << "dtor" << std::endl; }
private:
int dummy;
};
template <class T1, class T2>
class my_pair
{
public:
my_pair() {}
// Uncomment me plz !
//my_pair(const T1& x, const T2& y) : first(x), second(y) {}
template <class U1, class U2, class = typename std::enable_if<std::is_convertible<U1, T1>::value && std::is_convertible<U2, T2>::value>::type>
my_pair(U1&& x, U2&& y) : first(std::forward<U1>(x)), second(std::forward<U2>(y)) {}
public:
T1 first;
T2 second;
};
int main()
{
my_pair<int, test> tmp(5, test());
}
以上代码无法编译,因为 my_pair
的所谓 "perfect" 转发构造函数将临时 test
对象转发为右值引用,后者又试图显式调用删除了 test
.
的移动构造函数
如果我们从 my_pair
的 not so "perfect" 构造函数中删除注释,它会被重载决策所首选,并且基本上会强制复制临时 test
对象,从而使其成为工作。
我们以std::pair<T1, T2>
为例。它有以下两个构造函数:
constexpr pair( const T1& x, const T2& y ); // #1
template< class U1, class U2 > constexpr pair( U1&& x, U2&& y ); // #2
似乎#2 可以处理#1 可以处理的所有情况(没有更差的性能),除了参数是列表初始化器的情况。例如,
std::pair<int, int> p({0}, {0}); // ill-formed without #1
所以我的问题是:
如果#1 仅用于列表初始化器参数,因为
x
和y
最终绑定到从列表初始化器初始化的临时对象,为什么不使用constexpr pair( T1&& x, T2&& y );
代替?不然#1的真实意图是什么?
您回答了自己的问题。
pair( const T1& x, const T2& y );
在 之前 在 C++17 中添加了转发构造函数。 constexpr
是在 C++14 中添加的。现在为什么要保留构造函数?两个原因:1) 向后兼容性和 2) 因为正如您所说,格式完美的代码将拒绝编译。 C++ 非常强调向后兼容性,并采用非常保守的方法来弃用代码,因为它可能会以意想不到的方式中断。
现在,如果您想要一个更真实的示例,请尝试 std::for_each
。您可以将其更改为完美转发函子,而不会破坏代码。但是,添加此更改的动力很小,因为它被指定只制作一个副本。换句话说,更改的优先级不是很高。
如果你要存放的物品是临时物品,但不可移动怎么办?
#include <type_traits>
#include <utility>
#include <iostream>
class test
{
public:
test() { std::cout << "ctor" << std::endl; }
test(const test&) { std::cout << "copy ctor" << std::endl; }
test(test&&) = delete; // { std::cout << "move ctor" << std::endl; }
~test() { std::cout << "dtor" << std::endl; }
private:
int dummy;
};
template <class T1, class T2>
class my_pair
{
public:
my_pair() {}
// Uncomment me plz !
//my_pair(const T1& x, const T2& y) : first(x), second(y) {}
template <class U1, class U2, class = typename std::enable_if<std::is_convertible<U1, T1>::value && std::is_convertible<U2, T2>::value>::type>
my_pair(U1&& x, U2&& y) : first(std::forward<U1>(x)), second(std::forward<U2>(y)) {}
public:
T1 first;
T2 second;
};
int main()
{
my_pair<int, test> tmp(5, test());
}
以上代码无法编译,因为 my_pair
的所谓 "perfect" 转发构造函数将临时 test
对象转发为右值引用,后者又试图显式调用删除了 test
.
如果我们从 my_pair
的 not so "perfect" 构造函数中删除注释,它会被重载决策所首选,并且基本上会强制复制临时 test
对象,从而使其成为工作。