为何C++模板需显式特化?set_property编译报错排查
通用Setter模板函数编译错误分析与解决
问题描述
尝试为存储Foo对象的容器实现通用setter函数时,clang编译报错,存在两点疑惑:
- 明明可以将
std::string或字符串字面量传递给接收const std::string&参数的函数; - 注释掉的模板特化代码和通用模板逻辑完全一致,却能正常编译,不清楚问题出在哪。
代码示例
#include <iostream> #include <string> #include <vector> class Foo { protected: int _x; std::string _name; public: void set_x(int x) { _x = x; } void set_name(const std::string & name) { _name = name; } int x() const { return _x; } std::string name() const { return _name; } }; template <typename T, typename Container> void set_property(Container & container, const T & value, void (Foo::*setter)(T)) { for (auto & element : container) (element.*setter)(value); } // Uncomment this to succeed. // //template <typename Container> //void set_property(Container & container, const std::string & value, void (Foo::*setter)(const std::string &)) //{ // for (auto & element : container) // (element.*setter)(value); //} int main(void) { int x = 42; std::string name = "foo"; // Initialization. std::vector<Foo> foos(10); for (auto & foo : foos) { foo.set_x(x); foo.set_name(name); } x = 17; name = "bar"; // Set properties. set_property(foos, x, &Foo::set_x); set_property(foos, name, &Foo::set_name); std::cout << "x: " << foos[0].x() << std::endl; std::cout << "name: " << foos[0].name() << std::endl; return 0; }
编译错误信息
❯❯ g++ --std=c++20 foo.cpp &&./a.out foo.cpp:44:5: error: no matching function for call to 'set_property' set_property(foos, name, &Foo::set_name); ^~~~~~~~~~~~ foo.cpp:20:6: note: candidate template ignored: deduced conflicting types for parameter 'T' ('std::string' (aka 'basic_string<char>') vs. 'const std::string &' (aka 'const basic_string<char> &')) void set_property(Container & container, const T & value, void (Foo::*setter)(T)) ^ 1 error generated.
错误原因分析
1. 模板参数推导冲突
编译器处理set_property(foos, name, &Foo::set_name)时,会从两个渠道推导模板参数T:
- 从第二个参数
name(类型为std::string)推导,得到T = std::string; - 从第三个参数成员函数指针
&Foo::set_name(其参数类型是const std::string&)推导,得到T = const std::string&。
两个推导结果完全矛盾,导致模板参数推导失败,编译器找不到匹配的函数。
而set_x能正常编译,是因为set_x的参数是值传递的int,从x(int类型)和成员函数指针&Foo::set_x(参数int)推导的T都是int,没有冲突。
2. 特化版本能编译的原因
你注释掉的特化版本,直接把参数类型固定为const std::string&,不存在模板参数推导的过程,编译器能直接匹配到这个函数,自然不会出现推导冲突。
解决方法
方式一:拆分模板参数,独立推导
将模板参数拆分为两个,分别对应值的类型和成员函数的参数类型,两者互不干扰,编译器会自动处理隐式转换:
template <typename T, typename FuncParam, typename Container> void set_property(Container & container, const T & value, void (Foo::*setter)(FuncParam)) { for (auto & element : container) (element.*setter)(value); }
方式二:用std::decay统一推导类型
通过std::decay去掉成员函数指针参数的引用和const修饰,让推导结果一致:
#include <type_traits> template <typename T, typename Container> void set_property(Container & container, const T & value, void (Foo::*setter)(typename std::decay<T>::type)) { for (auto & element : container) (element.*setter)(value); }
内容的提问来源于stack exchange,提问作者duncan
相关产品推荐
相关产品推荐

