C++ 初始化列表重载消歧
C++ initializer list overload disambiguation
我有一个关于 C++ 初始化列表消歧的问题,它在 gcc、clang 和 Visual Studio 之间表现出不同的行为。
我想知道这是否是“未定义的行为”(不正确的程序)或者这些编译器中的一个是否有错误。有什么想法吗?
考虑以下声明:
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);
};
现在这个用法:
Object c("c", {4});
应该使用哪个构造函数?带有 int
的那个(假设文字周围的大括号是多余的)或带有初始化列表的那个(从 int
到 Arg
的隐式转换)。
GCC 10.2.0 选择初始化列表为 Arg
的构造函数。
Clang 11.2.2-2 选择带有 int
的构造函数并报告有关大括号的警告:
initlist.cpp:46:19: warning: braces around scalar initializer [-Wbraced-scalar-init]
Object c("c", {4});
^~~
Visual Studio 2019 16.8.6 在没有警告的情况下选择带有int
的构造函数(/W4
).
从多数观点来看,int
的构造函数获胜。另一方面,如果我们直接使用 std::initializer_list<int>
而不是 std::initializer_list<Arg>
(没有隐式调用 Arg
构造函数),所有三个编译器都会选择带有初始化列表的构造函数。
由于歧义和行为的差异,无论如何应该避免这种代码。但我很想知道谁错了?未定义的应用程序代码或编译器之一?
下面是完整的源代码,以防有人想尝试:
#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});
}
使用 GCC:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Arg(4)
Object("c", {4})
使用 clang 和 VS:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Object("c", 4)
{4}
到 const std::initializer_list<Arg>&
是用户定义的转换。
{4}
到 int
是标准转换。
后者获胜。这是一个 GCC 错误。
当转换序列具有相同形式时,initializer_list
的列表初始化优于其他。他们不在这里。
我有一个关于 C++ 初始化列表消歧的问题,它在 gcc、clang 和 Visual Studio 之间表现出不同的行为。
我想知道这是否是“未定义的行为”(不正确的程序)或者这些编译器中的一个是否有错误。有什么想法吗?
考虑以下声明:
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);
};
现在这个用法:
Object c("c", {4});
应该使用哪个构造函数?带有 int
的那个(假设文字周围的大括号是多余的)或带有初始化列表的那个(从 int
到 Arg
的隐式转换)。
GCC 10.2.0 选择初始化列表为 Arg
的构造函数。
Clang 11.2.2-2 选择带有 int
的构造函数并报告有关大括号的警告:
initlist.cpp:46:19: warning: braces around scalar initializer [-Wbraced-scalar-init]
Object c("c", {4});
^~~
Visual Studio 2019 16.8.6 在没有警告的情况下选择带有int
的构造函数(/W4
).
从多数观点来看,int
的构造函数获胜。另一方面,如果我们直接使用 std::initializer_list<int>
而不是 std::initializer_list<Arg>
(没有隐式调用 Arg
构造函数),所有三个编译器都会选择带有初始化列表的构造函数。
由于歧义和行为的差异,无论如何应该避免这种代码。但我很想知道谁错了?未定义的应用程序代码或编译器之一?
下面是完整的源代码,以防有人想尝试:
#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});
}
使用 GCC:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Arg(4)
Object("c", {4})
使用 clang 和 VS:
Object("a", 1)
Arg(2)
Arg(3)
Object("b", {2, 3})
Object("c", 4)
{4}
到 const std::initializer_list<Arg>&
是用户定义的转换。
{4}
到 int
是标准转换。
后者获胜。这是一个 GCC 错误。
当转换序列具有相同形式时,initializer_list
的列表初始化优于其他。他们不在这里。