C++可变参数模板解包为Tuple并使用结构化绑定的编译错误排查
我实现了一个带可变参数模板(variadic template)的类,希望编写成员函数将参数解包为tuple,以便通过结构化绑定(structured binding)访问单个参数。以下是我的实现代码:
#include <string> #include <tuple> #include <vector> #include <map> #include <memory> struct TypeErasedParameter { private: const std::string m_strId; protected: TypeErasedParameter(std::string id) : m_strId(id) {} public: const std::string & GetId() { return m_strId; } template <typename T> T& GetValue(); }; template <typename T> struct Parameter : public TypeErasedParameter { T parameterValue; Parameter(const std::string id, T value) : TypeErasedParameter(id), parameterValue(value) {} }; template <typename T> T& TypeErasedParameter::GetValue() { return dynamic_cast<Parameter<T>*>(this)->parameterValue; } // ParameterPack is basically a container that stores a bunch of TypeErasedParameters struct ParameterPack { std::map<std::string, std::unique_ptr<TypeErasedParameter>> m_mapParamsById; template<typename ParameterType> ParameterType& GetParameter(const std::string& strParamName) { return m_mapParamsById.at(strParamName)->GetValue<ParameterType>(); } }; template <typename ...Args> struct ParameterUnpacker { public: std::vector<std::string> m_vecIds; ParameterUnpacker(std::vector<std::string> vecIds) : m_vecIds(vecIds) {} const std::vector<std::string>& GetIds() { return m_vecIds; } template <typename Tuple, std::size_t... Is> std::unique_ptr<ParameterPack> CreateParameterPackHelper(Tuple&& tuple, std::index_sequence<Is...>) { std::unique_ptr<ParameterPack> pParamPack = std::unique_ptr<ParameterPack>(new ParameterPack()); const auto& vecIds = GetIds(); // TODO: This line is not correct yet, although the parameters provided are // the problem is the insertion of the elements I think if (!(pParamPack->m_mapParamsById.insert(std::make_pair(vecIds.at(Is), std::get<Is>(tuple))), ...)) { return nullptr; } return pParamPack; } template <typename... InputArgs> std::unique_ptr<ParameterPack> CreateParameterPack(InputArgs&... args) { return CreateParameterPackHelper(std::tuple<InputArgs...>{}, std::index_sequence_for<InputArgs ...>{}); } template<size_t... Is> std::tuple<Args& ...> UnpackParametersHelper( ParameterPack& parameters, std::index_sequence<Is...>) { const auto& vecIds = GetIds(); // TODO: Return a tuple of the parameters by matching the Ids with the Is order of the parameters return std::make_tuple< Args& ...>((parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is))) ...); } std::tuple<Args& ...> UnpackParameters( ParameterPack& parameters) { // parameters contains as many Ids and types as Args... return UnpackParametersHelper(parameters, std::index_sequence_for<Args...>{}); } }; int main(int argc, char* argv[]) { ParameterPack paramPack; paramPack.m_mapParamsById["doubleParam"] = std::make_unique<Parameter<double>>("doubleParam", 2.245); paramPack.m_mapParamsById["intParam"] = std::make_unique<Parameter<int>>("intParam", 5); ParameterUnpacker<double, int> unpacker({ "doubleParam" , "intParam" }); auto[doubleparamout, intparamout] = unpacker.UnpackParameters(paramPack); return 0; }
编译时触发如下错误:
"could not convert 'std::make_tuple(_Elements&& ...) [with _Elements = {double&, int&}]((* &(& parameters)->ParameterPack::GetParameter((* &(& vecIds)->std::vectorstd::__cxx11::basic_string
::at(1)))))' from 'tuple<double, int>' to 'tuple<double&, int&>'
return std::make_tuple< Args& ...>((parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is))) ...);"
错误原因
std::make_tuple会对传入的参数做完美转发,但显式指定模板参数为Args&...时,传入的左值引用会被折叠,最终生成持有值的tuple而非引用类型的tuple,与函数返回值类型不匹配。
修复方案
修改UnpackParametersHelper的返回语句,使用std::forward_as_tuple替代std::make_tuple——这个函数会保留参数的引用属性,直接生成引用类型的tuple:
template<size_t... Is> std::tuple<Args& ...> UnpackParametersHelper( ParameterPack& parameters, std::index_sequence<Is...>) { const auto& vecIds = GetIds(); return std::forward_as_tuple(parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is))...); }
如果坚持使用std::make_tuple,也可以用std::ref包装每个返回的引用:
return std::make_tuple(std::ref(parameters.GetParameter<typename std::tuple_element<Is, std::tuple<Args...>>::type>(vecIds.at(Is)))...);
额外修复(CreateParameterPack系列函数)
原代码中CreateParameterPack相关逻辑也存在问题:传入的参数引用被转为值类型tuple,且插入map的参数类型不匹配。修改后的示例如下:
template <typename Tuple, std::size_t... Is> std::unique_ptr<ParameterPack> CreateParameterPackHelper(Tuple&& tuple, std::index_sequence<Is...>) { auto pParamPack = std::make_unique<ParameterPack>(); const auto& vecIds = GetIds(); bool allInserted = true; // 用折叠表达式确保所有插入操作执行,并检查插入结果 ([&](){ auto [iter, inserted] = pParamPack->m_mapParamsById.insert( {vecIds.at(Is), std::make_unique<Parameter<std::decay_t<decltype(std::get<Is>(tuple))>>>(vecIds.at(Is), std::get<Is>(tuple))} ); if (!inserted) allInserted = false; }(), ...); return allInserted ? std::move(pParamPack) : nullptr; } template <typename... InputArgs> std::unique_ptr<ParameterPack> CreateParameterPack(InputArgs&&... args) { return CreateParameterPackHelper(std::forward_as_tuple(std::forward<InputArgs>(args)...), std::index_sequence_for<InputArgs ...>{}); }
内容的提问来源于stack exchange,提问作者kazbaem

