空std::initializer_list初始化与默认构造行为不一致问题求解
问题:Wrap类初始化行为不一致的修复方案
我定义了一个包含std::vector的Wrap类,支持默认构造(生成空vector)和通过花括号初始化列表传值构造。期望两种初始化形式行为一致:传入空列表时也得到空vector,但实际用{{}}初始化时,会构造出含一个默认元素的vector。
示例代码:
#include <iostream> #include <vector> struct Wrap { std::vector<double> v; Wrap() = default; Wrap(std::initializer_list<double> const &l) : v(l) {} }; int main() { Wrap v0 = {}; std::cout << v0.v.size() << "; expected 0\n"; Wrap v1 = {{}}; // expecting 0, getting 1 std::cout << v1.v.size() << "; expected 0\n"; }
已知这是std::initializer_list的预期行为,现寻求类重构方案满足:
- 默认构造生成空vector;
- 传入空花括号初始化列表时也生成空vector。
注:考虑过用T const (&)[N]的模板构造,但希望保留std::initializer_list的非窄化转换特性。
补充示例与困惑
以下代码展示了花括号初始化规则带来的矛盾:
#include <iostream> #include <vector> struct Wrap { std::vector<double> v; Wrap() { std::cout << "default constructor\n"; } Wrap(std::initializer_list<double> const &l) : v(l) { std::cout << "IL constructor\n"; } }; int main() { { Wrap v = {1, 2}; std::cout << v.v.size() << "; expected 2\n"; } { Wrap v = {{1, 2}}; std::cout << v.v.size() << "; expected 2, braces elision?\n"; } { Wrap v = {1}; std::cout << v.v.size() << "; expected 1\n"; } { Wrap v = {{1}}; std::cout << v.v.size() << "; expected 1, braces elision?\n"; } { Wrap v = {}; std::cout << v.v.size() << "; expected 0, default ctor has precedence other IL one\n"; } { Wrap v = {{}}; std::cout << v.v.size() << "; expected 0, braces elision?\n"; } }
程序输出:
Program returned: 0
IL constructor
2; expected 2
IL constructor
2; expected 2, braces elision?
IL constructor
1; expected 1
IL constructor
1; expected 1, braces elision?
default constructor
0; expected 0, default ctor has precedence other IL one
IL constructor
1; expected 0, braces elision?
解决方案
通过添加一个接受嵌套初始化列表的构造函数,利用重载解析优先级区分{{}}和普通初始化列表:
#include <iostream> #include <vector> struct Wrap { std::vector<double> v; Wrap() = default; Wrap(std::initializer_list<double> const &l) : v(l) {} // 匹配嵌套空列表{{}},初始化空vector Wrap(std::initializer_list<std::initializer_list<double>> const &l) : v() {} }; int main() { Wrap v0 = {}; // 默认构造,size 0 std::cout << v0.v.size() << "; expected 0\n"; Wrap v1 = {{}}; // 匹配嵌套初始化列表构造,size 0 std::cout << v1.v.size() << "; expected 0\n"; Wrap v2 = {1, 2}; // 普通初始化列表,size 2 std::cout << v2.v.size() << "; expected 2\n"; Wrap v3 = {{1, 2}}; // 花括号折叠,匹配普通初始化列表,size 2 std::cout << v3.v.size() << "; expected 2\n"; }
原理说明
- 当使用
{{}}初始化时,编译器会优先匹配接受std::initializer_list<std::initializer_list<double>>的构造函数,而非将其解析为含一个默认double元素的std::initializer_list<double>; - 对于非空的嵌套列表(如
{{1,2}}),编译器会进行花括号折叠,优先匹配更直接的std::initializer_list<double>构造函数,保留原有初始化行为; - 保留了
std::initializer_list的非窄化转换特性,同时解决了空嵌套列表的初始化问题。
内容的提问来源于stack exchange,提问作者Oersted
相关产品推荐
相关产品推荐

