能否用braced-init-list作为C++模板默认参数?兼容问题解析
我需要定义一个接受多个3D坐标的C++模板,如果把坐标各维度设为独立整型变量,参数列表会过长(3个坐标需9个参数),导致模板易用性下降。因此希望使用编译期数组声明模板,且默认参数直接以值而非变量名形式写在模板声明处。
测试发现GCC 13在std=c++20标准下可接受如下代码:
#include <cstdio> #include <array> template < std::array<int, 3> offset = {0, 0, 0} > struct Array { void operator() (int i, int j, int k) { printf("(%d, %d, %d)\n", i + offset[0], j + offset[1], k + offset[2]); } }; int main(void) { Array arr_default; arr_default(0, 0, 0); Array<{1, 1, 1}> arr; arr(0, 0, 0); return 0; }
但Clang 18会拒绝大括号初始化列表,报错信息如下:
test2.cpp:5:30: error: expected expression 5 | std::array<int, 3> offset = {0, 0, 0} | ^ test2.cpp:17:8: error: no viable constructor or deduction guide for deduction of template arguments of 'Array' 17 | Array arr_default; | ^ test2.cpp:7:8: note: candidate template ignored: couldn't infer template argument 'offset' 7 | struct Array | ^ test2.cpp:7:8: note: candidate function template not viable: requires 1 argument, but 0 were provided 7 | struct Array | ^~~~~ test2.cpp:20:8: error: expected expression 20 | Array<{1, 1, 1}> arr; | ^ 3 errors generated.
请问该程序是否为合法的C++程序?若合法,应使用何种语法让Clang接受?若不合法,如何修复代码,是否需要报告GCC无差别接受该代码的bug?
合法性判断
这段代码不合法。按照C标准,模板非类型参数的默认实参必须是常量表达式,而{0,0,0}是大括号初始化列表,不属于表达式范畴,不能直接用作默认实参;同理,模板实参列表里直接写{1,1,1}也不允许,因为模板实参要求是表达式,大括号列表不满足要求。GCC能接受是因为它做了非标准扩展,不符合C规范。
兼容Clang的修复方案
以下几种写法都符合标准,能被Clang和GCC同时接受:
方案1:显式构造std::array作为参数
把默认参数和模板实参都写成std::array<int,3>{0,0,0}这种完整的常量表达式形式:
#include <cstdio> #include <array> template < std::array<int, 3> offset = std::array<int, 3>{0, 0, 0} > struct Array { void operator() (int i, int j, int k) { printf("(%d, %d, %d)\n", i + offset[0], j + offset[1], k + offset[2]); } }; int main(void) { Array arr_default; arr_default(0, 0, 0); Array<std::array<int,3>{1, 1, 1}> arr; arr(0, 0, 0); return 0; }
方案2:使用constexpr数组作为模板参数(C++17+)
如果觉得std::array的写法繁琐,可改用constexpr数组作为非类型模板参数(需要C++17及以上标准支持):
#include <cstdio> template < const int offset[3] = {0,0,0} > struct Array { void operator() (int i, int j, int k) { printf("(%d, %d, %d)\n", i + offset[0], j + offset[1], k + offset[2]); } }; constexpr int ones[3] = {1,1,1}; int main(void) { Array arr_default; arr_default(0, 0, 0); Array<ones> arr; arr(0, 0, 0); return 0; }
方案3:自定义编译期结构体(可选)
如果需要更灵活的编译期坐标管理,也可以自定义一个包含三个整型成员的constexpr结构体作为模板参数,不过对于固定3维的场景,前两种方案更简洁。
关于GCC的行为
GCC接受该非标准代码是因为它实现了一个未被标准允许的扩展。这种行为会导致代码的可移植性下降,应该向GCC官方提交bug报告,指出其在标准模式下错误地接受了不符合规范的代码。
内容的提问来源于stack exchange,提问作者比尔盖子

