You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 16:57:02