コードがコンパイルに失敗する理由(明示的なテンプレートパラメーターがなくても)だけでなく、(私自身の意見では)コードが危険である理由について、@TartanLlamaの回答に関する詳細を少し追加します。
以下では、説明が簡単で意味が変わらないためT
、パラメーターパックの代わりに単純な型のみを使用します。Args...
転送参照に関するちょっとしたメモ...
まず、次の単純な例よりも簡単な例を見てみましょう。
template <typename T>
void f (T&&);
さて、f
さまざまなソースからインスタンス化しましょう。次の変数があると仮定しましょう。
std::string s;
const std::string cs;
...それから:
f(s); // instanciate f<std::string&>
f(cs); // instanciate f<const std::string&>
f(std::string()); // instanciate f<std::string&&>
あなたは疑問に思うはずです:なぜ最初のインスタンス化がf<std::string&>
ではなくf<std::string>
? 、しかし標準はあなたに伝えます(§14.8.2.1#3 [temp.deduct.call]):
P が転送参照で、引数が左辺値の場合、型推定のために A の代わりに「A への左辺値参照」型が使用されます。
最初のスニペットに戻りましょう。
ここで、例を少し複雑にしましょう。
template <typename T>
struct A {};
template <typename T>
void f (A<T>, T&&);
そして 1 つのインスタンス化:
std::string s;
A<std::string> as;
f(as, s);
上記はあなたの例と同等で、コンパイルに失敗しますが、なぜ... ? 上で説明したように、lvalueがある場合、推定された型T&&
はT&
ではないためT
、型推定は失敗します。A<T>
これは、コンパイラが期待していA<std::string&>
てA<std::string>
.
これで、次のことを行う必要があることがわかりました。
A<std::string&> ars;
A<std::string const&> acrs;
f(ars, s); // good
f(acrs, cs); // good
なぜ危険なのですか?
さて、これは良いはずです:
A<std::string&&> arrs;
f(arrs, std::string());
しかし、そうではありません... whenT
は右辺値参照として推定されるため、T
単純T
に であるため、コンパイラは を期待していA<std::string>
ます。
ここに問題があります:左辺値を期待する関数に転送するメソッドに右辺値を与えようとしています。それは間違いではありませんが、おそらくあなたが期待していたものではありません。
それに対処する方法は?
最初の可能性は、 の推定型に関係なく、最初のパラメーターの型を強制することですT
。たとえば、次のようになります。
template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);
ただし、次の点に注意してください。
- 対処するものをさらに追加する必要があり
const
ます。
- 最初の引数の型が固定されている場合の有用性を疑問に思うかもしれません
T&&
(少なくともあなたの場合)。
2 番目の可能性 (警告: これが標準かどうかはわかりません! ) は、最初のパラメーターを最後に移動してから、型を推測することですt
。
template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);
T
これで、 の推定型と の期待型が完全に一致しましたA
。
残念ながら、上記を可変個引数テンプレートで機能させる方法がわかりません...