cra*_*yze 6 c++ visual-c++ language-lawyer c++11 clang++
我一直以为当我使用初始化列表C++语法时:
something({ ... });
Run Code Online (Sandbox Code Playgroud)
编译器总是清楚我想要调用过载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;
}
Run Code Online (Sandbox Code Playgroud)
并且在GCC 6.3中它按预期工作,呼叫 Test::Test(std::initializer_list)
但在MSVC 2015中,此代码调用Test::Test(int).
似乎MSVC可以以某种方式忽略{}并选择无效/意外的重载来调用.
标准对这种情况有何看法?哪个版本有效?
任何人都可以对此进行测试并确认此问题是否仍然存在于MSVC 2017中?
哪个版本有效?
根据我对标准的理解,海湾合作委员会是对的.
标准对这种情况有何看法?
您在编写时所执行的操作Test obj1({10});是使用表达式直接初始化类型的对象.在重载解析期间,编译器必须决定调用哪个构造函数.根据16.3.3.2§3(3.1.1)[over.ics.rank]:Test{ 10 }
列表初始化序列
L1是比列表初始化序列更好的转换序列,L2如果L1转换std::initializer_list<X>为某些X并且L2不[...]
该标准还提供了示例
void f1(int); // #1
void f1(std::initializer_list<long>); // #2
void g1() { f1({42}); } // chooses #2
Run Code Online (Sandbox Code Playgroud)
这是VS&clang与GCC不同的地方:虽然这三个在这个特定的例子中会产生相同的结果,但是将它改为
#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});
}
Run Code Online (Sandbox Code Playgroud)
会让clang选择int-constructor,对文字周围不必要的括号抱怨42(这似乎只是标准遗留原因,请参见此处),而不是检查{ 42 }列表序列是否真的无法转换为std::initializer_list<A>.
但请注意,写入Test obj1{ 10 };将导致不同的评估:根据列表初始化的规则:
- 否则,T的构造函数分为两个阶段:
- 将std :: initializer_list作为唯一参数的所有构造函数,或者作为第一个参数,如果其余参数具有默认值,将检查所有构造函数,并通过重载决策与std :: initializer_list类型的单个参数进行匹配
因此initializer_list构造函数用于特殊的重载解析阶段initializer_list,在应用正常的重载分辨率之前仅考虑构造函数,如着名的std::vector-gotcha所示:
// will be a vector with elements 2, 0 rather than a vector of size 2 with values 0, 0
std::vector<int> v{ 2, 0 };
Run Code Online (Sandbox Code Playgroud)
事实上,在两种情况下,标准决定使用initializer_list构造函数是一致的选择,但从技术上讲,选择它的原因在引擎盖下是完全不同的.
GCC在这里错了。
事实上,由于括号是直接初始化,因此适用“正常”重载规则,但是,[over.ics.rank]/3.1讨论了这种情况:
void f1(int); // #1
void f1(std::initializer_list<long>); // #2
void g1() { f1({42}); } // chooses #2
Run Code Online (Sandbox Code Playgroud)
而在我们的情况下,我们有这样的情况:
struct IntSimilar { IntSimilar(int); };
void f1(size_t); // #1
void f1(std::initializer_list<IntSimilar>); // #2
void g1() { f1({10}); } // chooses ?
Run Code Online (Sandbox Code Playgroud)
还有另一条规则,[over.ics.rank]/2就在 [over.ics.rank]/3 之前:
— 标准转换序列是比用户定义的转换更好的转换序列
为了调用Test(initializer_list<IntSimilar>)用户定义的转换,需要 ( intto IntSimilar)。但有一个更好的可行替代方案,特别是从到 的整数转换。这是可能的,因为标量(例如 )可以从带有单个元素的花括号初始化列表进行列表初始化。请参阅[dcl.init.list]/3.9:intsize_tintint
— 否则,如果初始值设定项列表具有类型 E 的单个元素,并且 T 不是引用类型或其引用类型与 E 引用相关,则从该元素初始化对象或引用...
clang 实际上会准确地告诉你这一点(在选择重载时int):
void f1(int); // #1
void f1(std::initializer_list<long>); // #2
void g1() { f1({42}); } // chooses #2
Run Code Online (Sandbox Code Playgroud)
如果您想禁止自动展开单值braced-init-list,请使用list-initialization或将其包装到另一个braced-init-list中:
testing::Test<testing::IntSimilar> obj { 10 };
testing::Test<testing::IntSimilar> obj({{10}});
Run Code Online (Sandbox Code Playgroud)
- 将在各处选择initializer_list<T>过载。
| 归档时间: |
|
| 查看次数: |
308 次 |
| 最近记录: |