我一直认为当我使用初始化列表C ++语法时:
something({ ... });
编译器总是清楚我想调用带有std::initializer_list
的重载,但对于MSVC 2015来说似乎并不那么清楚。
我测试了这个简单的代码:
#include <cstdio>
#include <initializer_list>
namespace testing {
template<typename T>
struct Test {
Test() {
printf("Test::Test()\n");
}
explicit Test(size_t count) {
printf("Test::Test(int)\n");
}
Test(std::initializer_list<T> init) {
printf("Test::Test(std::initializer_list)\n");
}
T* member;
};
struct IntSimilar {
int val;
IntSimilar() : val(0) {}
IntSimilar(int v) : val(v) {}
operator int() {
return val;
}
};
}
int main() {
testing::Test<testing::IntSimilar> obj({ 10 });
return 0;
}
并且在GCC 6.3中它按预期工作,调用Test::Test(std::initializer_list)
但在MSVC 2015中,此代码调用Test::Test(int)
。
似乎MSVC可以以某种方式忽略{}
并选择无效/意外超载来调用。
标准对这种情况有何看法?哪个版本有效?
有人可以对此进行测试并确认此问题是否仍然存在于MSVC 2017中?
答案 0 :(得分:5)
哪个版本有效?
根据我对该标准的理解, GCC 正确。
标准对这种情况有何看法?
您在撰写Test obj1({10});
时所执行的操作direct-initializing是Test
类型的对象,其表达式为{ 10 }
。在重载解析期间,编译器必须决定调用哪个构造函数。根据{{3}}:
列表初始化序列
L1
是一个比列表初始化序列L2
更好的转换序列,如果L1
转换为 某些std::initializer_list<X>
和X
的{{1}}不会[...]
该标准还提供了示例
L2
这是VS&amp; amp; clang与GCC不同:虽然这三个将在这个特定的例子中产生相同的结果,但将其改为
void f1(int); // #1
void f1(std::initializer_list<long>); // #2
void g1() { f1({42}); } // chooses #2
会让16.3.3.2 § 3 (3.1.1) [over.ics.rank]抱怨文字#include <iostream>
struct A { A(int) { } };
void f1(int) { std::cout << "int\n"; } // #1
void f1(std::initializer_list<A>) { std::cout << "list\n"; } // #2
int main() {
f1({42});
}
周围不必要的大括号(由于遗留原因似乎只是标准,请参阅clang chose the int
-constructor),而不是检查{ {1}}列表序列实际上无法转换为42
。
但请注意,撰写{ 42 }
会导致不同的评价:根据here的规则:
- 否则,T的构造函数分为两个阶段:
- 检查以std :: initializer_list为唯一参数的所有构造函数,或者如果其余参数具有默认值,则作为第一个参数,并针对std :: initializer_list类型的单个参数进行重载匹配
所以std::initializer_list<A>
构造函数用于特殊的重载决策阶段,在应用正常的重载决策之前只考虑Test obj1{ 10 };
构造函数,如着名的initializer_list
- gotcha:
initializer_list
事实上,在这两种情况下,标准决定使用std::vector
构造函数是一个一致的选择,但从技术上讲,选择它的原因是完全不同的。
答案 1 :(得分:0)
GCC在这里错了。
实际上由于括号,它是直接初始化所以“正常”的重载规则适用,但是,[over.ics.rank]/3.1谈论这种情况:
void f1(int); // #1
void f1(std::initializer_list<long>); // #2
void g1() { f1({42}); } // chooses #2
在我们的情况下,我们有:
struct IntSimilar { IntSimilar(int); };
void f1(size_t); // #1
void f1(std::initializer_list<IntSimilar>); // #2
void g1() { f1({10}); } // chooses ?
在[over.ics.rank] / 3之前还有另一条规则[over.ics.rank]/2:
- 标准转换序列是比用户定义转换更好的转换序列
要调用Test(initializer_list<IntSimilar>)
,需要用户定义的转换(int
到IntSimilar
)。
但是有一个更好的可行替代方案,特别是从int
到size_t
的整数转换。这是可能的,因为标量(例如int
)可以使用单个int
元素从 braced-init-list 进行列表初始化。见[dcl.init.list]/3.9:
- 否则,如果初始化列表具有E类型的单个元素且T不是引用类型或其引用类型与E引用相关,则从该元素初始化对象或引用...
clang实际上会告诉你(在选择int
重载时):
warning: braces around scalar initializer [-Wbraced-scalar-init]
如果要禁止单值 braced-init-list 的自动解包,请使用 list-initialization 或将其包装到另一个 braced- INIT-列表:
testing::Test<testing::IntSimilar> obj { 10 };
testing::Test<testing::IntSimilar> obj({{10}});
- 将在任何地方选择initializer_list<T>
重载。