4

次のコードがあります。

#include <iostream>
#include <typeinfo>

template <typename T>
struct A : T {
    template <typename ...Args>
    A(Args&&... params) : T(std::forward<Args>(params)...), x(0) {
        std::cout << "Member 'x' was default constructed\n"; 
    }

    template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
    A(O o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
        std::cout << "Member 'x' was constructed from arguments\n"; 
    }

    int x;
};

struct B{
    B(const char*) {}
};

int main() {
    A<B> a("test");
    A<B> y(3, "test");

    return 0;
}

正常に動作し、印刷されます

Member 'x' was default constructed
Member 'x' was constructed from arguments

ただし、2 番目のオーバーロードの最初の引数が参照の場合、突然 2 番目のオーバーロードが取得されなくなり、コンパイルが失敗します。

template <typename O, typename ...Args, typename = typename std::enable_if<std::is_constructible<int,O>::value>::type>
    A(O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
        std::cout << "Member 'x' was constructed from arguments\n"; 
    } // Note the O& in the arguments

どうしてこれなの?それを修正してコピーを避けることは可能ですか?

編集:ユニバーサル参照を使用すると、明らかに再び機能します。私が実際に望んでいるものであるconst参照も機能しません。

さらに、入力パラメーターを別の値に保存しても (右辺値を避けて)、機能しません。

int main() {
    double x = 3.0;
    A<B> y(x, "test"); // Still not working

    return 0;
}
4

1 に答える 1

8

どうしてこれなの?

次の宣言の場合:

template <typename O>
A(O& o);

呼び出し:

A{3};

Oタイプが であると推測されるintため、次のインスタンス化になります。

A(int& o);

しかし、あなたがしていることは、右辺値(3確かにそうです) をこのインスタンス化された非 const 左辺値参照にバインドしようとしていることです。これは許可されていません

それを修正してコピーを避けることは可能ですか?

o型を転送参照として宣言し、forwardそれをコンストラクターに宣言することもできますx(ただし、このようなプリミティブ型の場合int、実際にはまったく必要ありません)。

template <typename O>
A(O&& o) : x{std::forward<O>(o)} {}

別の方法として、コンストラクターを const 左辺値参照として宣言することもできます (右辺値をそれによってバインドできるようにするため)。

template <typename O>
A(const O& o) : x{o} {}

ユニバーサル参照を使用すると問題は解決しますが、残念ながら const 参照 (これは実際に私が望んでいたものです) では解決しません。さらに、入力パラメーターを別の値に保存しても (右辺値を避けて)、機能しません。

これは、ほとんどの場合、ユニバーサル参照が正確な一致を生成し、ユニバーサル参照を取得する最初のコンストラクターがオーバーロード解決手順で実行可能な最適な関数であるためです。

右辺値を渡す場合、推定されるint&&は よりも右辺値に適していconst int&ます。

左辺値を渡す場合、推測されint&たものは、 より非 const 左辺値 (変数 などx)に適していますconst int&

そうは言っても、ユニバーサル参照を取得するこの貪欲なコンストラクターは、インスタンス化するときに次の理由から、どちらの場合でも実行可能な最良の関数です。

template <typename... Args>
A(Args&&... params);

template <typename O, typename... Args>
A(const O& z, Args&&... params);

たとえば、次の呼び出しの場合:

double x = 3.0;
A a(x, "test");

コンパイラは次のようになります。

A(double&, const char (&)[5]);

A(const double&, const char (&)[5]);

最初の署名の方がより一致constします (修飾を追加する必要はありません)。

なんらかの理由で、このO型をテンプレート化する必要がある場合 (これがユニバーサル参照であるか const lvalue 参照であるかに関係なく)、最初の引数が可能な場合、オーバーロード解決手順から最初の貪欲なコンストラクターを無効にする必要があります。構築するために使用されますint(2番目のものがそのような条件下で有効になるのと同じように):

template <typename T>
struct A : T
{
    template <typename Arg, typename... Args, typename = typename std::enable_if<!std::is_constructible<int, Arg>::value>::type>
    A(Arg&& param, Args&&... params) : T(std::forward<Arg>(param), std::forward<Args>(params)...), x(0) {
        std::cout << "Member 'x' was default constructed\n"; 
    }

    template <typename O, typename... Args, typename = typename std::enable_if<std::is_constructible<int, O>::value>::type>
    A(const O& o, Args&&... params) : T(std::forward<Args>(params)...), x(o) {
        std::cout << "Member 'x' was constructed from arguments\n"; 
    }

    int x;
};

デモ

于 2014-09-23T17:38:16.023 に答える