Почему 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
Какой компилятор здесь правильно? (или, может быть, они оба правильны, если это некоторая форма поведения undefined)
Ответы
Ответ 1
Я верю, что результат clang правильный.
В bar_t a { foo }
direct-list-initialization и в static_cast между пользовательскими типами конструкторы типа назначения рассматриваются перед пользовательскими операторами преобразования в исходном типе (С++ 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>
.
Код, который в настоящее время написан, зависит от гарантированного разрешения на копирование С++ 17; Если вы скомпилируете без гарантированного копирования С++ 17 (например, -std=c++14
), то clang отвергает этот код из-за инициализации копирования в bar_t b = static_cast<bar_t>(foo);
.