I have a question about C++ initializer list disambiguation which exhibits different behaviors between gcc, clang and Visual Studio.
I wonder if this is "undefined behavior" (incorrect program) or if one of these compilers has a bug. Any idea?
Consider the following declarations:
class Arg
{
public:
Arg(int i);
};
class Object
{
public:
Object(const char* str, int i);
Object(const char* str, const std::initializer_list<Arg>& args);
};
And now this usage:
Object c("c", {4});
Which constructor should be used? The one with int
(assuming that the braces around the literal are superfluous) or the one with the initializer list (with implicit conversion from int
to Arg
).
GCC 10.2.0 chooses the constructor with the initializer list of Arg
.
Clang 11.2.2-2 chooses the constructor with int
and reports a warning about the braces:
initlist.cpp:46:19: warning: braces around scalar initializer [-Wbraced-scalar-init]
Object c("c", {4});
^~~
Visual Studio 2019 16.8.6 chooses the constructor with int
without warning (/W4
).
On a majority standpoint, the constructor with int
wins. On the other hand, if we directly use std::initializer_list<int>
instead of std::initializer_list<Arg>
(no implicit call to Arg
constructor), all three compilers choose the constructor with the initializer list.
Because of the ambiguity and the difference of behavior, this kind of code should be avoided anyway. But I am curious to understand who is wrong? The undefined application code or one of the compilers?
Full source code below, in case anyone wants to try:
#include <iostream>
class Arg
{
public:
int value;
Arg(int i);
};
class Object
{
public:
Object(const char* str, int i);
Object(const char* str, const std::initializer_list<Arg>& args);
};
Arg::Arg(int i) : value(i)
{
std::cout << "Arg(" << i << ")" << std::endl;
}
Object::Object(const char* str, int i)
{
std::cout << "Object(\"" << str << "\", " << i << ")" << std::endl;
}
Object::Object(const char* str, const std::initializer_list<Arg>& args)
{
std::cout << "Object(\"" << str << "\", {";
bool comma = false;
for (auto it = args.begin(); it != args.end(); ++it) {
if (comma) {
std::cout << ", ";
}
comma = true;
std::cout << it->value;
}
std::cout << "})" << std::endl;
}
int main(int argc, char* argv[])
{
Object a("a", 1);
Object b("b", {2, 3});
Object c("c", {4});
}
With GCC:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Arg(4)
Object("c", {4})
With clang and VS:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Object("c", 4)
{4}
is perceived as a structured type, an aggregate, not an elementary type. Anyway, that is the rule... – Gonium