考虑一个用于检查参数值等的“契约(Contract)”功能:

  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方法吗?有没有可能的陷阱?是否有不需要重复的解决方案?

最佳答案

严格来说,这不是您问题的答案,但是我不认为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这样的东西...

09-25 20:19