含std::string引用的参数包模板实参推导失败问题求助
带引用参数的命名构造函数无法通过模板函数实例化
问题描述
我写了一个Command类,通过命名构造函数创建实例,为减少栈开销希望用const std::string&传递参数。生产代码中有多种继承自基类的Command类型,因此编写模板函数instantiate封装std::shared_ptr的冗余创建逻辑。但调用带引用参数的命名构造函数时,编译器无法推导模板参数,提示参数包不一致。直接调用带引用的命名构造函数正常,传值版本配合instantiate也正常,唯独带引用的版本失败,求原因及解决办法。
代码示例
#include <memory> #include <string> class Command { public: static Command* named_constructor_str_ref(const std::string &t) { return new Command(t); } static Command* named_constructor(const std::string t) { return new Command(t); } ~Command() = default; private: explicit Command(const std::string &t) : text(t) {} const std::string text; }; template <typename T, typename... Ts> std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args) { if(named_ctr != nullptr) { return std::shared_ptr<T>(named_ctr(args...)); } return nullptr; } int main(void) { const std::string s = "My text"; // 直接调用带引用的命名构造函数正常 auto c1 = std::shared_ptr<Command>(Command::named_constructor_str_ref(s)); // 传值版本配合instantiate正常 auto c2 = instantiate(Command::named_constructor, s); // 带引用版本配合instantiate失败 auto c3 = instantiate(Command::named_constructor_str_ref, s); return 0; }
编译错误信息
<source>: In function 'int main()': <source>:45:26: error: no matching function for call to 'instantiate(Command* (&)(const std::string&), const std::string&)' 45 | auto c3 = instantiate(Command::named_constructor_str_ref, s); | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ <source>:24:26: note: candidate: 'template<class T, class ... Ts> std::shared_ptr<Command> instantiate(T* (*)(Ts ...), Ts ...)' 24 | std::shared_ptr<Command> instantiate(T *(*named_ctr)(Ts...), Ts... args) | ^~~~~~~~~~~ <source>:24:26: note: template argument deduction/substitution failed: <source>:45:26: note: inconsistent parameter pack deduction with 'const std::__cxx11::basic_string<char>&' and 'std::__cxx11::basic_string<char>' 45 | auto c3 = instantiate(Command::named_constructor_str_ref, s); | ~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
原因分析
模板函数instantiate的Ts...参数包被用于两处推导:
- 从命名构造函数的指针签名推导:
named_constructor_str_ref的参数是const std::string&,所以Ts被推导为const std::string& - 从传入的实参
s推导:s是const std::string类型,所以Ts被推导为const std::string
两种推导结果矛盾,编译器无法统一Ts的类型,因此报错。
解决方法
将模板的参数拆分为两组:一组对应函数指针的参数类型,另一组对应调用时传入的实参类型,让两组独立推导,避免冲突。同时用std::forward实现完美转发,保证参数传递的正确性。
修改后的代码:
#include <memory> #include <string> #include <utility> class Command { public: static Command* named_constructor_str_ref(const std::string &t) { return new Command(t); } static Command* named_constructor(const std::string t) { return new Command(t); } ~Command() = default; private: explicit Command(const std::string &t) : text(t) {} const std::string text; }; template <typename T, typename... FuncArgs, typename... CallArgs> std::shared_ptr<Command> instantiate(T *(*named_ctr)(FuncArgs...), CallArgs&&... args) { if(named_ctr != nullptr) { return std::shared_ptr<T>(named_ctr(std::forward<CallArgs>(args)...)); } return nullptr; } int main(void) { const std::string s = "My text"; auto c1 = std::shared_ptr<Command>(Command::named_constructor_str_ref(s)); auto c2 = instantiate(Command::named_constructor, s); auto c3 = instantiate(Command::named_constructor_str_ref, s); // 现在可正常编译 return 0; }
如果使用C++17及以上标准,还可以用更简洁的写法,直接让编译器推导函数指针类型:
template <typename T, typename Func, typename... Args> std::shared_ptr<Command> instantiate(Func named_ctr, Args&&... args) { if(named_ctr != nullptr) { return std::shared_ptr<T>(named_ctr(std::forward<Args>(args)...)); } return nullptr; }
内容的提问来源于stack exchange,提问作者RaKol
相关产品推荐
相关产品推荐

