std::initializer_list何时可作为constexpr?编译器行为差异解析
std::initializer_list的constexpr行为分歧与标准合规性
根据cppreference说明,std::initializer_list自C14起拥有constexpr构造函数和constexpr size()方法,但实际使用中,GCC、Clang、MSVC三大主流编译器对其constexpr属性的支持存在分歧,以下通过四个测试案例分析符合C标准的正确行为。
测试代码
#include <initializer_list> using size_type = std::initializer_list<int>::size_type; template <typename T> size_type Foo(std::initializer_list<T> const &list) { return list.size(); } int main() { // 1. 基于cppreference示例的static_assert // gcc: 编译通过 // clang: 报错,无可行构造函数或推导指引 // msvc: 编译通过 static_assert(std::initializer_list{1, 2, 3}.size() == 3); // 2. 推导T并创建constexpr std::initializer_list<T> // gcc: 报错,不是常量表达式 // clang: 报错,无可行构造函数或推导指引 // msvc: 编译通过 constexpr auto the_list = std::initializer_list{1, 2, 3}; // 3. 使用constexpr对象的size()做static_assert // gcc: 因案例2失败而报错 // clang: 因案例2失败而报错 // msvc: 编译通过 static_assert(the_list.size() == 3); // 4. 通过函数提取constexpr对象的size // gcc: 因案例2失败而报错 // clang: 因案例2失败而报错 // msvc: 报错,表达式未求值为常量 constexpr auto the_list_size = Foo(the_list); return 0; }
测试环境
- GCC:x86-64 GCC(trunk,含13.1版本),编译选项
-std=c++20 - Clang:x86-64 Clang(trunk,含16.0.0版本),编译选项
-std=c++20 - MSVC:x64 MSVC v19.latest,编译选项
/std:c++20
标准解读与编译器正误判断
1. 类模板实参推导(CTAD)问题
C17引入类模板实参推导,std::initializer_list作为类模板,标准明确允许从初始化列表的元素类型推导模板参数T。因此案例1和2中std::initializer_list{1,2,3}应推导出T=int,Clang报错“无可行构造函数或推导指引”不符合C17及以后标准,属于实现缺陷。
2. constexpr std::initializer_list的合法性
C++14起,std::initializer_list的构造函数被标记为constexpr,允许在常量表达式中创建实例。案例2中GCC报错“不是常量表达式”不符合标准——编译期可以为初始化列表创建临时数组并绑定到std::initializer_list对象,这是标准明确允许的行为,GCC此处存在实现限制。
3. 非constexpr函数的常量表达式调用限制
案例4中,Foo函数未声明为constexpr,即使传入constexpr对象,在常量表达式中调用非constexpr函数也不符合标准,因此MSVC的报错是正确的。若要让案例4合法,需修改Foo为constexpr函数:
template <typename T> constexpr size_type Foo(std::initializer_list<T> const &list) { return list.size(); }
总结
- Clang在案例1、2中的报错属于实现缺陷,不符合C++17标准。
- GCC在案例2中的报错属于实现限制,不符合C++14标准。
- MSVC在案例4中的报错符合标准,是代码本身未将
Foo声明为constexpr导致的问题。 - 案例1、2、3的标准预期行为应为编译通过,案例4需修改
Foo为constexpr后才能编译通过。
内容的提问来源于stack exchange,提问作者Adrian McCarthy
相关产品推荐
相关产品推荐

