是否需要为const和通用引用重载?

Are overloads for const and universal references necessary?

本文关键字:引用 重载 const 是否      更新时间:2023-10-16

考虑一个用于检查参数值等的"契约"函数:

  template< class T >
  const T& AssertNotEmpty( const T& val )
  {
    //raise hell if val empty/0/...
    return val;
  }

例如可以这样使用:

void foo( const std::shared_ptr< int >& val )
{
  AssertNotEmpty( val );
  //use *val
}
class Bar98
{
public:
  Bar98( const std::shared_ptr< int >& val ) : myVal( AssertNotEmpty( val ) ) {}
private:
  std::shared_ptr< int > myVal;
};
std::shared_ptr< int > x;
//...
AssertNotEmpty( x ); //(1)

现在进入c++ 11,我们希望Bar98按值获取构造函数参数并从中移动:

class Bar11
{
public:
  Bar11( std::shared_ptr< int > val ) :
    myVal( AssertNotEmpty( std::move( val ) ) )
  {}
private:
  std::shared_ptr< int > myVal;
};

要使这个工作AssertNotEmpty需要重写,我相当天真地认为通过使用通用引用可以工作:

  template< class T >
  T&& AssertNotEmpty( T&& val )
  {
    //...
    return std::move( val );
  }

这似乎对所有情况都很好,除了最后一个(1),其中VS给出warning C4239: nonstandard extension used : 'return' : conversion from 'std::shared_ptr<int>' to 'std::shared_ptr<int> &'。据我所知,这是因为编译器看到AssertNotEmpty( x )AssertNotEmpty( T& && x ),它崩溃成AssertNotEmpty( T& ),你不能从T&移动,如果我错了,请纠正我。

为了解决这个问题,我添加了通用引用作为重载,仅对非左值引用启用,以强制编译器在遇到普通左值引用时也选择const引用,如(1):

  template< class T >
  const T& AssertNotEmpty( const T& val )
  {
    //...
    return val;
  }
  template< class T >
  T&& AssertNotEmpty( T&& val, typename std::enable_if< !std::is_lvalue_reference< T >::value, int >::type* = 0 )
  {
    //...
    return std::move( val );
  }

似乎按预期工作,编译器在我尝试的所有情况下选择正确的一个,但这是"正确的"c++ 11解决这个问题的方法吗?有什么可能的陷阱吗?难道没有不需要复制的解决方案吗?

我不认为你应该从那个函数返回任何东西。然而,这可能会达到您的目的。

template<class T>
auto AssertNotEmpty(T&& val) -> decltype(std::forward<T>(val))
{
    //...
    return std::forward<T>(val);
}

这不是严格回答你的问题,但我不认为AssertNotEmpty应该修改它的参数也不返回任何东西。由于逗号操作符,您仍然可以在构造函数中使用它,例如:

template< class T >
void AssertNotEmpty( T const& val )
{
  /* assert( val not empty ) */
}
class Bar11
{
public:
  Bar11( std::shared_ptr< int > val ) :
    myVal( ( AssertNotEmpty( val ), std::move( val ) ) )
  {}
private:
  std::shared_ptr< int > myVal;
};

注意,额外的括号是必需的,所以两个表达式求值,结果是最后一个表达式的结果。

否则,应该重命名函数。我想到了AssertNotEmptyThenMove之类的东西