当一个完美的转发构造函数存在时,前导左值引用构造函数的意图是什么?

问题描述 投票:7回答:2

我们以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可以处理的所有情况(没有更差的性能),除了参数是list-initializer的情况。例如,

std::pair<int, int> p({0}, {0}); // ill-formed without #1

所以我的问题是:

  • 如果#1仅用于list-initializer参数,由于xy最终绑定到从list-initializers初始化的临时对象,为什么不使用constexpr pair( T1&& x, T2&& y );呢?
  • 否则,#1的实际意图是什么?
c++ constructor move-semantics rvalue-reference perfect-forwarding
2个回答
3
投票

如果要存储的对象是临时对象但不可移动怎么办?

#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所谓的“完美”转发构造函数将临时test对象转发为右值引用,后者又试图调用test的显式删除的移动构造函数。

如果我们从my_pair不是那么“完美”的构造函数中删除注释,则优先选择重载决策并基本上强制临时test对象的副本,从而使其工作。


1
投票

你是在自问自答。

在C ++ 17中添加转发构造函数之前,pair( const T1& x, const T2& y );就在那里。 constexpr是在C ++ 14中添加的。现在为什么要保持构造函数?有两个原因:1)向后兼容性和2)因为如你所说,完美格式化的代码会拒绝编译。 C ++非常强调向后兼容性,并采用非常保守的方法来弃用代码,因为它可能会以意想不到的方式中断。

现在,如果你想要一个更现实的例子,试试std::for_each。您可以将其更改为完美转发仿函数而不会破坏代码。但是,添加此更改没有什么动力,因为它被指定只生成一个副本。换句话说,改变的优先级不是很高。

© www.soinside.com 2019 - 2024. All rights reserved.