for compiler compile c++ gcc clang language-lawyer compiler-bug

c++ - compiler - ¿Por qué gcc y clang producen resultados diferentes para este programa?(operador de conversión vs constructor)



llvm clang for windows (1)

programa:

#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); }

salida para gcc 7/8:

2,2

salida para clang 4/5 (también para gcc 6.3)

1,1

Parece que sucede lo siguiente al crear las instancias de bar_t :

Para gcc , calls foo_t::operator bar_t luego constructs bar_t with T = int .

Para clang , constructs bar_t with T = foo_t luego calls foo_t::operator int

¿Qué compilador es correcto aquí? (o tal vez ambos son correctos si esto es alguna forma de comportamiento indefinido)


Creo que el resultado del clang es correcto.

Tanto en bar_t a { foo } inicialización de lista directa como en static_cast entre tipos definidos por el usuario, los constructores del tipo de destino se consideran antes de los operadores de conversión definidos por el usuario en el tipo de fuente (C ++ 14 [dcl.init.list] / 3 [expr.static.cast] / 4). Si la resolución de sobrecarga encuentra un constructor adecuado, entonces se usa.

Al hacer una resolución de sobrecarga, bar_t::bar_t<foo_t>(const foo_t&) es viable y será una mejor coincidencia que una instancia de cualquier instancia de esta plantilla, lo que resulta en el uso de los operadores de elenco en foo. También será mejor que cualquier constructor declarado por defecto, ya que toman algo que no sea foo_t , por lo que se bar_t::bar_t<foo_t> .

El código tal como está escrito en la actualidad depende de la elisión de copia garantizada de C ++ 17; Si compila sin la elisión de copia garantizada de C ++ 17 (por ejemplo, -std=c++14 ), entonces clang rechaza este código debido a la inicialización de la copia en bar_t b = static_cast<bar_t>(foo); .