web-dev-qa-db-ja.com

gccとclangがそれぞれこのプログラムに対して異なる出力を生成するのはなぜですか? (変換演算子とコンストラクター)

プログラム:

#include <stdio.h>

struct bar_t {
    int value;
    template<typename T>
    bar_t (const T& t) : value { t } {}

    // edit: You can uncomment these if your compiler supports
    //       guaranteed copy elision (c++17). Either way, it 
    //       doesn't affect the output.

    // bar_t () = delete;
    // bar_t (bar_t&&) = delete;
    // bar_t (const bar_t&) = delete;
    // bar_t& operator = (bar_t&&) = delete;
    // bar_t& operator = (const bar_t&) = delete;
};

struct foo_t {
    operator int   () const { return 1; }
    operator bar_t () const { return 2; }
};

int main ()
{
    foo_t foo {};
    bar_t a { foo };
    bar_t b = static_cast<bar_t>(foo);

    printf("%d,%d\n", a.value, b.value);
}

gcc 7/8の出力:

2,2

clang 4/5の出力(gcc 6.3の場合も)

1,1

bar_tのインスタンスを作成するとき、次のことが起こっているようです:

gccの場合は、calls foo_t::operator bar_tに続いてconstructs bar_t with T = intになります。

clangの場合、constructs bar_t with T = foo_tに続いてcalls foo_t::operator int

ここで正しいコンパイラはどれですか? (または、これが何らかの形の未定義の動作である場合、両方とも正しいかもしれません)

34
verb_noun

Clangの結果は正しいと思います。

_bar_t a { foo }_ direct-list-initializationとユーザー定義型間のstatic_castの両方で、ソース型のユーザー定義変換演算子の前に宛先型のコンストラクターが考慮されます(C++ 14 [dcl.init.list]/3 [expr.static.cast]/4)。オーバーロード解決が適切なコンストラクターを見つけると、それが使用されます。

オーバーロード解決を行う場合、bar_t::bar_t<foo_t>(const foo_t&)は実行可能であり、このテンプレートのインスタンス化のいずれよりも一致し、fooでキャスト演算子が使用されます。 _foo_t_以外のものを使用するため、デフォルトで宣言されたコンストラクターよりも優れているため、_bar_t::bar_t<foo_t>_が使用されます。


現在記述されているコードは、C++ 17の保証されたコピーの省略に依存しています。 C++ 17の保証されたコピー省略なしでコンパイルした場合(例__-std=c++14_)、clangはbar_t b = static_cast<bar_t>(foo);のコピー初期化によりこのコードを拒否します。

18
bames53