单/嵌套std::initializer_list构造函数在Clang/GCC中歧义,MSVC无此问题
二维矩阵Initializer_list构造函数的跨编译器歧义问题及解决
我实现了一个二维矩阵类Matrix2D,原本的嵌套std::initializer_list构造函数可以正常创建常规二维矩阵。为了简化行向量(n行1列)的创建,我新增了单std::initializer_list<T>构造函数,这样就能用Matrix2D<int> x{1,2,3}替代繁琐的Matrix2D<int> x{{1},{2},{3}}。
这段代码在MSVC(包括constexpr场景)中能正常编译,但GCC和Clang会报构造函数歧义。我认为嵌套列表的区分逻辑清晰,MSVC的行为符合预期。
测试代码
// Comment out next line for only nested initializer_list ctor #define INCLUDE_EXTRA_CTOR #include <memory> #include <numeric> #include <initializer_list> #include <exception> #include <stdexcept> #include <concepts> using std::size_t; template <typename T> class Matrix2D { T* const pv; // pointer to matrix contents public: const size_t cols; const size_t rows; // default ctor; Matrix2D() noexcept : pv(nullptr), cols(0), rows(0) {} // 2D List initialized ctor Matrix2D(std::initializer_list<std::initializer_list<T>> list) : pv((list.begin())->size() != 0 ? new T[list.size() * (list.begin())->size()] : nullptr), cols(pv != nullptr ? (list.begin())->size() : 0), rows(pv != nullptr ? list.size() : 0) { if (pv == nullptr) return; for (size_t row = 0; row < list.size(); row++) { if (cols != (list.begin() + row)->size()) throw std::runtime_error("number of columns in each row must be the same"); for (size_t col = 0; col < cols; col++) pv[cols * row + col] = *((list.begin() + row)->begin() + col); } } #ifdef INCLUDE_EXTRA_CTOR // Row initialized ctor, rows=n, cols=1; Matrix2D(std::initializer_list<T> list) : pv(list.size() != 0 ? new T[list.size()] : nullptr), cols(pv != nullptr ? 1 : 0), rows(pv != nullptr ? list.size() : 0) { if (pv == nullptr) return; for (size_t row = 0; row < rows; row++) { pv[row] = *(list.begin() + row); } } #endif // dtor ~Matrix2D() { delete[] pv; } }; int main() { // Tests of various possible null declarations Matrix2D<int> x1{ }; // default Matrix2D<int> x2{ {} }; // E0309, nested init list with 1 row, 0 cols, forced to 0 rows, 0 cols Matrix2D<int> x3{ {},{} }; // E0309, nested init list with 2 rows, 0 cols, forced to 0 rows, 0 cols // typical declaration Matrix2D<int> x4{ {1,2},{3,4},{5,6} }; // nested init list with 3 rows, 2 cols // standard row vector declaration Matrix2D<int> x5{ {1},{2},{3} }; // E0309, init list with 3 rows, 1 col #ifdef INCLUDE_EXTRA_CTOR // row vector declaration Matrix2D<int> x6{ 1,2,3 }; // init list with 3 rows, 1 col #endif }
注:E0309是MSVC智能感知提示的歧义错误,但实际编译可通过。
歧义原因
GCC和Clang严格遵循C++标准的初始化列表重载决议规则:
- 当处理
{{1},{2},{3}}这类嵌套初始化列表时,编译器有两种匹配路径:一是直接匹配std::initializer_list<std::initializer_list<T>>构造函数;二是将每个内层{1}隐式转换为T(此处为int),从而匹配std::initializer_list<T>构造函数。由于std::initializer_list允许隐式构造,这就导致了重载歧义。 - MSVC对嵌套初始化列表做了特殊优先级处理,优先选择层数匹配的构造函数,但这种行为并不符合C++标准的严格重载决议逻辑。
解决办法
1. 标签分派显式区分构造函数
新增一个标签结构体,让行向量构造函数必须通过标签触发,彻底避免歧义:
struct RowVectorTag {}; // 修改行向量构造函数 Matrix2D(RowVectorTag, std::initializer_list<T> list) : pv(list.size() != 0 ? new T[list.size()] : nullptr), cols(pv != nullptr ? 1 : 0), rows(pv != nullptr ? list.size() : 0) { if (pv == nullptr) return; for (size_t row = 0; row < rows; row++) { pv[row] = *(list.begin() + row); } }
创建行向量时显式传入标签:
Matrix2D<int> x6{RowVectorTag{}, 1, 2, 3};
2. 用SFINAE限制构造函数匹配范围
通过std::enable_if(C++11及以后)让单列表构造函数仅在元素无法转换为std::initializer_list<T>时生效:
#ifdef INCLUDE_EXTRA_CTOR // Row initialized ctor, rows=n, cols=1; template <typename U = T> Matrix2D(std::initializer_list<T> list, std::enable_if_t<!std::is_convertible_v<T, std::initializer_list<U>>, void*> = nullptr) : pv(list.size() != 0 ? new T[list.size()] : nullptr), cols(pv != nullptr ? 1 : 0), rows(pv != nullptr ? list.size() : 0) { if (pv == nullptr) return; for (size_t row = 0; row < rows; row++) { pv[row] = *(list.begin() + row); } } #endif
3. 辅助函数简化行向量创建
保留原嵌套列表构造函数,新增辅助函数将单行列表转换为嵌套列表:
template <typename T> Matrix2D<T> make_row_vector(std::initializer_list<T> list) { std::vector<std::initializer_list<T>> nested_rows; nested_rows.reserve(list.size()); for (const auto& elem : list) { nested_rows.push_back({elem}); } return Matrix2D<T>(std::initializer_list<std::initializer_list<T>>(nested_rows.data(), nested_rows.size())); }
使用方式:
auto x6 = make_row_vector<int>({1, 2, 3});
4. C++20+用构造函数约束
利用C++20的概念(Concepts)直接限制单列表构造函数的元素类型:
#ifdef INCLUDE_EXTRA_CTOR // Row initialized ctor, rows=n, cols=1; Matrix2D(std::initializer_list<T> list) requires (!std::convertible_to<T, std::initializer_list<T>>) : pv(list.size() != 0 ? new T[list.size()] : nullptr), cols(pv != nullptr ? 1 : 0), rows(pv != nullptr ? list.size() : 0) { if (pv == nullptr) return; for (size_t row = 0; row < rows; row++) { pv[row] = *(list.begin() + row); } } #endif
这种方式最简洁,完全符合现代C++标准的设计思路。
内容的提问来源于stack exchange,提问作者doug
相关产品推荐
相关产品推荐

