C++20中使用Constructible_from概念实现包装类的初始化列表问题
问题描述
我希望创建一个与被包装类行为完全一致的包装类,使用C++20的std::constructible_from概念实现后出现编译错误。代码示例如下:
#include<vector> #include<concepts> template<class C1> struct wrapper{ C1 p1; wrapper():p1(){}; wrapper(wrapper&& o): p1(std::move(o.p1)){}; ~wrapper(){p1.~C1();}; template<class ...C> wrapper(C... c)requires std::constructible_from<C1,C...>:p1(std::forward<C>(c)...){}; }; int main() { using w=wrapper<std::vector<float>>; w a(5,1.2); w b{std::vector<float>{1.2,1.7,1.9}}; //w c{1.2,1.7,1.9}; error:no matching constructor //w d{{1.2,1.7,1.9}}; error:no matching constructor std::vector<float> e{{1.2,1.7,1.9}}; return 0; }
当前代码部分构造方式可行,但初始化列表相关构造无法工作。需解答:
- 如何让包装类与被包装的
std::vector行为完全一致? - 是否需要单独提供初始化列表构造函数?
- 为何默认模板构造不满足需求?
解决方案与解释
问题3:为什么模板构造函数无法处理初始化列表?
核心原因有两点:
{1.2,1.7,1.9}这类**大括号初始化列表(braced-init-list)**不属于任何具体类型,模板参数推导无法为C...推导出匹配的类型,导致std::constructible_from<C1, C...>约束不满足,模板构造函数不会被纳入候选列表。- 对于
w d{{1.2,1.7,1.9}},由于wrapper包含用户声明的构造函数,它不再是聚合类型,无法通过聚合初始化直接初始化成员p1;而std::vector本身依赖专门的std::initializer_list构造函数,你的包装类未实现该逻辑,自然无法匹配。
问题2:需要单独提供初始化列表构造函数吗?
是的,必须单独声明。std::initializer_list构造是C++的特殊构造逻辑,无法通过普通模板参数推导覆盖,只有显式添加对应的构造函数,才能支持大括号初始化列表的场景。
问题1:如何让包装类与被包装类行为完全一致?
需要对代码做以下关键修改:
添加
std::initializer_list构造函数
针对被包装类的初始化列表构造,添加模板版本的构造函数,确保能匹配任意可用于构造C1的std::initializer_list<U>:template<class U> requires std::constructible_from<C1, std::initializer_list<U>> wrapper(std::initializer_list<U> il) : p1(il) {}修复析构函数问题
手动调用p1.~C1()会导致双重析构(成员p1本身会在对象销毁时自动调用析构函数),属于未定义行为,必须删除手动编写的析构函数,让编译器自动生成即可。补充默认复制/移动构造
由于手动声明了移动构造函数,编译器会自动删除默认复制构造函数,需要显式声明默认复制/移动构造来保证值语义完整:wrapper(const wrapper&) = default; wrapper(wrapper&&) = default;优化模板构造的转发逻辑
原模板构造的参数是C... c,改为C&&... c配合std::forward能更准确地实现完美转发,避免不必要的拷贝。
修改后的完整代码:
#include<vector> #include<concepts> #include<utility> template<class C1> struct wrapper{ C1 p1; // 默认构造 wrapper() : p1() {}; // 默认复制/移动构造 wrapper(const wrapper&) = default; wrapper(wrapper&&) = default; // 默认析构(可省略,编译器自动生成) ~wrapper() = default; // 完美转发普通构造参数 template<class ...C> requires std::constructible_from<C1, C...> wrapper(C&&... c) : p1(std::forward<C>(c)...) {}; // 支持初始化列表构造 template<class U> requires std::constructible_from<C1, std::initializer_list<U>> wrapper(std::initializer_list<U> il) : p1(il) {} }; int main() { using w=wrapper<std::vector<float>>; w a(5,1.2); w b{std::vector<float>{1.2,1.7,1.9}}; w c{1.2,1.7,1.9}; // 现在可正常编译 w d{{1.2,1.7,1.9}}; // 现在可正常编译 std::vector<float> e{{1.2,1.7,1.9}}; return 0; }
额外说明:如果被包装类还有其他特殊构造(比如带分配器的构造),你可能需要补充对应的模板构造函数,但上述修改已经覆盖了绝大多数常见场景,能让包装类的构造行为与被包装类保持一致。
内容的提问来源于stack exchange,提问作者user11566470
相关产品推荐
相关产品推荐

