9

今日、オーバーロード解決のかなり奇妙なケースに遭遇しました。私はそれを次のように減らしました:

struct S
{
    S(int, int = 0);
};

class C
{
public:
    template <typename... Args>
    C(S, Args... args);

    C(const C&) = delete;
};

int main()
{
    C c({1, 2});
}

可変引数の数がゼロで、オブジェクトの初期化子リスト構築として扱われるC c({1, 2})の最初のコンストラクターと一致することを完全に期待していました。C{1, 2}S

ただし、C の削除されたコピー コンストラクターと一致することを示すコンパイラ エラーが表示されます。

test.cpp: In function 'int main()':
test.cpp:17:15: error: use of deleted function 'C(const C &)'
test.cpp:12:5: error: declared here

私はそれがどのように機能するかを見ることができます.Cの有効な{1, 2}初期化子として解釈できます.可変引数です...しかし、特に問題のコピーコンストラクターが削除されていることを考えると、なぜそれがより良い一致になるのかわかりません。1S2

ここで行われているオーバーロードの解決規則を説明して、コンストラクター呼び出しで S の名前を言及しない回避策があるかどうか教えてください。

編集: 誰かがスニペットが別のコンパイラでコンパイルされると述べたので、GCC 4.6.1 で上記のエラーが発生したことを明確にする必要があります。

EDIT 2:スニペットをさらに単純化して、さらに不穏な失敗を引き起こしました:

struct S
{
    S(int, int = 0);
};

struct C
{
    C(S);
};

int main()
{
    C c({1});
}

エラー:

test.cpp: In function 'int main()':
test.cpp:13:12: error: call of overloaded 'C(<brace-enclosed initializer list>)' is ambiguous
test.cpp:13:12: note: candidates are:
test.cpp:8:5: note: C::C(S)
test.cpp:6:8: note: constexpr C::C(const C&)
test.cpp:6:8: note: constexpr C::C(C&&)

今回は、GCC 4.5.1 でも同じエラーが発生します (constexpr暗黙的に生成されない s とムーブ コンストラクタを除く)。

これが言語設計者の意図したものであるとは信じがたいです...

4

2 に答える 2

6

C c({1, 2});使用できるコンストラクターが 2 つあります。したがって、オーバーロードの解決が行われ、どの関数を使用するかが調べられます

C(S, Args...)
C(const C&)

Argsあなたが考え出したように、ゼロに推定されます。したがって、コンパイラは、構築と一時的な out のS構築を比較します。fromの構築は簡単で、宣言された のコンストラクターを使用します。また、コンストラクターからの構築は簡単で、コンストラクター テンプレートを使用します (コピー コンストラクターは、パラメーターが 1 つしかなく、2 つの引数 -と- が渡されるため実行できません)。これら 2 つの変換シーケンスは比較できません。したがって、最初のコンストラクターがテンプレートであるという事実がなければ、2 つのコンストラクターはあいまいになります。そのため、GCC は非テンプレートを優先し、削除されたコピー コンストラクターを選択して診断を行います。C{1, 2}S{1, 2}SC{1, 2}12

C c({1});テストケースでは、3 つのコンストラクターを使用できます

C(S)
C(C const&)
C(C &&)

最後の 2 つは、右辺値を右辺値にバインドするため、コンパイラは 3 番目を優先します。ただし、逆に考えるC(S)C(C&&)、2 つのパラメーター型の間で勝者を見つけることはできません。なぜなら、 forから aをC(S)構築でき、 for はコンストラクターを使用してaから一時的なものを初期化できるためです(標準では、パラメーターのユーザー定義の変換を明示的に禁止しています)。からのクラスオブジェクトの初期化に使用できるムーブまたはコピー コンストラクター。S{1}C(C&&)C{1}C(S)C{...}1C&&1S)。しかし今回は、最初のテストケースとは対照的に、どちらのコンストラクターもテンプレートではないため、あいまいになります。

これは完全に、物事が機能することを意図した方法です。C++ での初期化は奇妙です。そのため、すべての人がすべてを「直感的」に理解することは、残念ながら不可能です。上記のような単純な例でさえ、すぐに複雑になります。この回答を書いて 1 時間後にもう一度見たとき、何かを見落としていることに気づき、回答を修正する必要がありました。

于 2011-10-24T20:38:15.347 に答える
4

Cその初期化子リストから を 作成できる理由についての解釈は正しいかもしれません。ideone は喜んでサンプル コードをコンパイルしますが、どちらのコンパイラも正しくありません。ただし、コピーの作成が有効であると仮定すると...

したがって、コンパイラの観点からは、2 つの選択肢があります: 新規作成しS{1,2}てテンプレート化されたコンストラクターを使用するか、新規作成しC{1,2}てコピー コンストラクターを使用します。原則として、非テンプレート関数はテンプレート関数よりも優先されるため、コピー コンストラクターが選択されます。 次に、関数を呼び出すことができるかどうかを調べます...できないため、エラーを吐き出します。

SFINAE には別の種類のエラーが必要です...最初のステップで、どの関数が一致する可能性があるかを確認するときにエラーが発生します。関数を作成しただけでエラーが発生した場合、そのエラーは無視され、関数はオーバーロードの可能性があると見なされません。考えられる過負荷が列挙された後、このエラー抑制はオフになり、取得したものに固執します。

于 2011-10-24T04:22:58.430 に答える