为何clang++与g++中std::initializer_list构造函数调用行为不同?
GCC与Clang列表初始化构造函数选择差异解析
问题重现
测试代码(补充遗漏头文件后):
#include <iostream> #include <initializer_list> struct Foo { Foo() {} Foo(std::initializer_list<Foo>) { std::cout << "initializer list" << std::endl; } Foo(const Foo&) { std::cout << "copy ctor" << std::endl; } }; int main() { Foo a; Foo b(a); Foo c{a}; }
使用g++ -std=c++20 -o main main.cpp编译运行输出:
copy ctor copy ctor initializer list
使用clang++ -std=c++20 -o main main.cpp编译运行输出:
copy ctor copy ctor
差异根源
这个差异是列表初始化重载决议规则的编译器实现分歧,结合C++17起的强制复制消除机制共同导致的:
1. 列表初始化的标准决议逻辑
根据C++标准,非聚合类的列表初始化分两阶段执行重载决议:
- 阶段1:仅考虑类的初始化列表构造函数(参数为
std::initializer_list<T>的构造),将整个初始化列表作为单个参数匹配。 - 阶段2:若阶段1无可行构造函数,再将初始化列表元素作为普通参数,匹配所有构造函数。
2. 编译器的实现差异
针对Foo c{a}这一行:
- GCC行为:严格遵循两阶段决议,阶段1中
{a}可被隐式转换为std::initializer_list<Foo>,因此直接选中初始化列表构造函数,执行后输出对应日志。 - Clang行为:对单个同类型对象的列表初始化做特殊处理,跳过阶段1的初始化列表构造匹配,直接进入阶段2选择拷贝构造函数。
3. 强制复制消除的影响
C++17及以后标准规定:当用同类型对象直接初始化新对象时,编译器必须省略拷贝/移动构造函数的调用,哪怕构造函数有可见副作用(比如这里的cout输出)。因此Clang选中拷贝构造函数后,直接跳过了调用,没有产生额外输出。
验证方式
若修改拷贝构造函数为非const版本:
Foo(Foo&) { std::cout << "non-const copy ctor" << std::endl; }
Clang会因无法绑定const引用报错,证明它确实在尝试匹配拷贝构造函数;而GCC仍能正常编译,输出initializer list。
内容的提问来源于stack exchange,提问作者wowonline
相关产品推荐
相关产品推荐

