GCC与Clang对std::vector构造参数转发的差异及正确性探讨
C++列表初始化vector的编译器差异分析
问题背景
定义如下结构体Foo:
#include <vector> #include <numeric> #include <ranges> struct Foo{ Foo(int x) : x{x}{} template<std::ranges::range R> Foo(R r) : x{std::accumulate(r.begin(), r.end(), 0, [](int x, const auto& foo){return x + foo.x;})}{} int x; };
执行以下初始化代码时:
std::vector<Foo> foos{Foo{1}, Foo{2}, Foo{3}}; std::vector<Foo> foos_copy{foos};
不同编译器出现行为差异:
- Clang中,
foos_copy是foos的完整副本,两者元素数量、值完全一致; - GCC中,
foos_copy仅包含1个元素,效果等价于std::vector<Foo> foos_copy{{foos}}——即把foos传给Foo的范围构造函数生成单个Foo对象,再作为vector的初始元素。
差异产生的原因
核心矛盾在于列表初始化时的构造函数重载决议逻辑分歧:
当执行std::vector<Foo> foos_copy{foos}时,编译器需要从std::vector的候选构造函数中选择最匹配的:
- 复制构造函数:
vector(const vector&),参数为const std::vector<Foo>&,与foos(左值)是完全匹配的身份转换,无需额外转换。 - 初始化列表构造函数:
vector(std::initializer_list<Foo>),要求初始化列表中的元素能转换为Foo。由于std::vector<Foo>满足std::ranges::range概念,Foo的模板构造函数Foo(R r)可行,因此foos可以通过用户定义转换转为Foo,进而组成仅含一个元素的初始化列表。
- Clang遵循标准重载决议规则,优先选择转换序列更优的复制构造函数(身份转换优于用户定义转换);
- GCC错误地优先选择了初始化列表构造函数,忽略了复制构造函数的更优匹配性。
标准符合性判定
根据C++标准的[over.match.list]章节规则:
- 列表初始化时,候选构造函数包含初始化列表构造函数,以及当初始化列表仅含一个元素时,所有能接受该元素作为单个参数的构造函数(包括复制构造函数);
- 重载决议需比较转换序列的优劣,身份转换的优先级远高于用户定义转换。
因此,Clang的行为符合C++标准,GCC的行为属于编译器实现错误。
内容的提问来源于stack exchange,提问作者fabmene
相关产品推荐
相关产品推荐

