如何向含默认与非默认构造元素的tuple转发构造参数?
问题:混合默认/带参构造的std::tuple容器初始化失败
我尝试实现一个用std::tuple存储多类型对象的类OpsSequence。当所有tuple元素都拥有带参构造函数时,能通过容器构造函数正确初始化;但如果其中一个元素仅拥有默认构造函数、其余元素为带参构造,初始化tuple会触发编译错误。
相关代码
#include <tuple> #include <string> struct Op1WithArg { Op1WithArg(int){} }; struct Op2WithArg { Op2WithArg(std::string){} }; struct OpWithoutArg { OpWithoutArg(){} }; template <class... Ops> struct OpsSequence { using OpTuple = std::tuple<Ops...>; template<typename... Args> OpsSequence(Args... args) : mOps{args...} {} OpTuple mOps; }; void TestSequenceWithOpWithoutArg() { // 此代码可正常运行 using SequenceType = OpsSequence<OpWithoutArg>; SequenceType sequence{}; } void TestSequenceWithTwoOpsWithArg() { // 此代码可正常运行 using SequenceType = OpsSequence<Op1WithArg, Op2WithArg>; SequenceType sequence{1, "Hello"}; } void TestSequenceWithOneOpWithArgAndOneWithout() { // 此代码编译失败 using SequenceType = OpsSequence<Op1WithArg, OpWithoutArg>; SequenceType sequence{1,{}}; }
编译错误信息
|| [ 50%] Building CXX object CMakeFiles/tsconv_tests.dir/tests/tsconv_tests.cpp.o || /home/anders/src/tsconv/tests/tsconv_tests.cpp: 函数‘void tsconv::TestSequenceWithOneOpWithArgAndOneWithout()’中: tests/tsconv_tests.cpp|1014 col 31| 错误:找不到匹配的函数调用‘tsconv::OpsSequence<tsconv::Op1WithArg, tsconv::OpWithoutArg>::OpsSequence(<大括号初始化列表>)’ || 1014 | SequenceType sequence{1,{}}; || | ^ tests/tsconv_tests.cpp|989 col 5| 提示:候选函数:‘tsconv::OpsSequence<Ops>::OpsSequence(Args ...) [with Args = {}; Ops = {tsconv::Op1WithArg, tsconv::OpWithoutArg}]’ || 989 | OpsSequence(Args... args) || | ^~~~~~~~~~~ tests/tsconv_tests.cpp|989 col 5| 提示: 候选函数期望0个参数,实际提供2个 tests/tsconv_tests.cpp|985 col 8| 提示:候选函数:‘constexpr tsconv::OpsSequence<tsconv::Op1WithArg, tsconv::OpWithoutArg>::OpsSequence(const tsconv::OpsSequence<tsconv::Op1WithArg, tsconv::OpWithoutArg>&)’ || 985 | struct OpsSequence { || | ^~~~~~~~~~~ tests/tsconv_tests.cpp|985 col 8| 提示: 候选函数期望1个参数,实际提供2个 tests/tsconv_tests.cpp|985 col 8| 提示:候选函数:‘constexpr tsconv::OpsSequence<tsconv::Op1WithArg, tsconv::OpWithoutArg>::OpsSequence(tsconv::OpsSequence<tsconv::Op1WithArg, tsconv::OpWithoutArg>&&)’ tests/tsconv_tests.cpp|985 col 8| 提示: 候选函数期望1个参数,实际提供2个 gmake[2]: *** [CMakeFiles/tsconv_tests.dir/build.make|76| CMakeFiles/tsconv_tests.dir/tests/tsconv_tests.cpp.o] Error 1 gmake[1]: *** [CMakeFiles/Makefile2|83| CMakeFiles/tsconv_tests.dir/all] Error 2 gmake: *** [Makefile|101| all] Error 2
解决方案
编译失败的核心原因是:模板构造函数的参数推导无法处理{}这种无类型的初始化器——{}属于非推导上下文,编译器无法推导出对应的Args类型,导致无法匹配到构造函数。
方案1:改为聚合类(最简修复)
移除自定义构造函数,让OpsSequence成为聚合类,编译器会自动使用聚合初始化规则,直接将初始化列表传递给内部的std::tuple成员,完美适配混合默认/带参构造的场景:
#include <tuple> #include <string> // 结构体定义不变 template <class... Ops> struct OpsSequence { using OpTuple = std::tuple<Ops...>; // 移除自定义构造函数,依赖聚合初始化 OpTuple mOps; }; // 所有测试函数均可正常编译运行 void TestSequenceWithOpWithoutArg() { using SequenceType = OpsSequence<OpWithoutArg>; SequenceType sequence{}; } void TestSequenceWithTwoOpsWithArg() { using SequenceType = OpsSequence<Op1WithArg, Op2WithArg>; SequenceType sequence{1, "Hello"}; } void TestSequenceWithOneOpWithArgAndOneWithout() { using SequenceType = OpsSequence<Op1WithArg, OpWithoutArg>; SequenceType sequence{1,{}}; // 现在可以正常编译 }
方案2:保留构造函数的进阶修复
如果必须保留自定义构造函数(比如需要在构造时执行额外逻辑),可以修改构造函数,通过完美转发传递参数,并添加类型约束确保参数能正确构造tuple:
#include <tuple> #include <string> #include <type_traits> // 结构体定义不变 template <class... Ops> struct OpsSequence { using OpTuple = std::tuple<Ops...>; template<typename... Args, typename = std::enable_if_t<std::is_constructible_v<OpTuple, Args&&...>>> OpsSequence(Args&&... args) : mOps(std::forward<Args>(args)...) {} OpTuple mOps; };
这种写法下,sequence{1, {}}也能正常编译,因为std::is_constructible_v会验证参数是否能构造OpTuple,而std::forward确保参数正确传递。
内容的提问来源于stack exchange,提问作者Anders Lindstrom
相关产品推荐
相关产品推荐

