次のコードを検討してください
struct A {
A(int id) : id_ { id } {}
A(const A& rhs) { std::cout << "cctor from " +
std::to_string(rhs.id_) << std::endl; }
A(A&& rhs) { std::cout << "mctor from " +
std::to_string(rhs.id_) << std::endl; }
int id_;
};
template<typename T>
struct B1 {
constexpr B1(T&& x) noexcept : x_ { std::forward<T>(x) } {}
T x_;
};
template<typename T>
struct B2 {
constexpr B2(T&& x) noexcept;
T x_;
};
template<typename T>
constexpr
B2<T>::B2(
T&& x
) noexcept :
x_ { std::forward<T>(x) } {
}
int
main(
) {
A a { 1 };
//B1 b11 { a }; // Not compiling
B1 b12 { A { 2 } };
B2 b21 { a };
B2 b22 { A { 3 } };
return 0;
}
これは
mctor from 2
mctor from 3
したがって、基本的には、外部で定義されたコンストラクターが引数の値カテゴリを完全に転送するのに対し、インラインで定義されたコンストラクターは転送しないように見えます。
外部で定義されたコンストラクターが関数テンプレート(引数を完全に転送する)のように処理されるのでしょうか、それともここで何が起こっているのでしょうか?
規格の適切なセクションへのリンクを歓迎します。
GCC7.2.0を使用しています。
これはGCCのバグです。転送参照には、非常に明確な定義があります。
[temp.deduct.call](私の強調)
A転送参照は、テンプレートパラメーターを表さないcv非修飾テンプレートパラメーターへの右辺値参照です。クラステンプレートの(クラステンプレート引数の推定中([over.match.class.deduct]))。 Pが転送参照であり、引数が左辺値である場合、型の推定にはAの代わりに「Aへの左辺値参照」型が使用されます。
どちらの場合も、T
はCTAD中に囲んでいるクラスのテンプレートパラメータに名前を付けるため、どちらの方法でも転送参照を生成しないでください。インラインまたはクラス定義の外部で定義されているc'torは、これとは関係ありません。
GCCは、自動生成された控除ガイドのT&&
を転送参照として誤って処理しているようです。
template <typename T>
B2(T&& x) -> B2<T>;
この場合、T&&
はクラスパラメータであるため、転送しないr値参照です。代わりに、GCCはT=A&
パラメーター型とB2<T>=B2<A&>
クラス型を誤って推定します。これにより、コンストラクターの参照型が折りたたまれ、左辺値コンストラクター引数を使用してコードをコンパイルできます。
constexpr B2(A& x) noexcept;
クラステンプレートの引数の推定では、インライン定義とオフライン定義を区別しません。この特定のケースでは、B2 b21 { a };
は失敗するはずです。