引数などの値をチェックするために使用される「コントラクト」関数を考えてみましょう:
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)
ここで、Bar98 にコンストラクター引数を値で取得させ、そこから移動させたい C++11 に入ります。
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 );
}
これは、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&
です。間違っている場合は修正してください。
これを修正するために、ユニバーサル参照をオーバーロードとして追加しました。これは、(1) のような単純な左辺値参照に遭遇した場合にもコンパイラに const 参照を選択させるために、非左辺値参照に対してのみ有効になります。
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 の方法ですか? 落とし穴の可能性はありますか?複製を必要としない解決策はありませんか?