web-dev-qa-db-ja.com

コンストラクターでの完全な転送(C ++ 17)

次のコードを検討してください

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を使用しています。

13
plexando

これはGCCのバグです。転送参照には、非常に明確な定義があります。

[temp.deduct.call](私の強調)

A転送参照は、テンプレートパラメーターを表さないcv非修飾テンプレートパラメーターへの右辺値参照です。クラステンプレートの(クラステンプレート引数の推定中([over.match.class.deduct]))。 Pが転送参照であり、引数が左辺値である場合、型の推定にはAの代わりに「Aへの左辺値参照」型が使用されます。

どちらの場合も、TはCTAD中に囲んでいるクラスのテンプレートパラメータに名前を付けるため、どちらの方法でも転送参照を生成しないでください。インラインまたはクラス定義の外部で定義されているc'torは、これとは関係ありません。

9
StoryTeller

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 };は失敗するはずです。

7
Piotr Skotnicki