program:
#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); }
output for gcc 7/8:
2,2
output for clang 4/5 (also for gcc 6.3)
1,1
It seems that the following is happening when creating the instances of bar_t
:
For gcc, it calls foo_t::operator bar_t
then constructs bar_t with T = int
.
For clang, it constructs bar_t with T = foo_t
then calls foo_t::operator int
Which compiler is correct here? (or maybe they are both correct if this is some form of undefined behaviour)
TL;DR: Clang is highly compatible to GCC - just give it a go. In most cases, Clang could be used as a GCC drop in replacement ( clang and clang++ are "GCC compatible drivers"). Clang was designed with GCC compatiblity in mind, and most options available in GCC should also be supported by Clang.
Clang is compatible with GCC. Its command-line interface shares many of GCC's flags and options. Clang implements many GNU language extensions and compiler intrinsics, some of which are purely for compatibility.
I believe clang's result is correct.
In both bar_t a { foo }
direct-list-initialization and in a static_cast between user defined types, constructors of the destination type are considered before user defined conversion operators on the source type (C++14 [dcl.init.list]/3 [expr.static.cast]/4). If overload resolution finds a suitable constructor then it's used.
When doing overload resolution bar_t::bar_t<foo_t>(const foo_t&)
is viable and will be a better match than one to any instantiation of this template resulting in the use of the cast operators on foo. It will also be better than any default declared constructors since they take something other than foo_t
, so bar_t::bar_t<foo_t>
is used.
The code as currently written depends on C++17 guaranteed copy elision; If you compile without C++17's guaranteed copy elision (e.g. -std=c++14
) then clang does reject this code due to the copy-initialization in bar_t b = static_cast<bar_t>(foo);
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With