C++可变参数模板无法找到合适构造函数的原因及解决方案
问题复现
template<class T> struct Test { T data; constexpr Test()=default; Test(Test&)=delete; void Write() { } template<typename...Args> void Write(unsigned int v, Args...args) { Write(args...); } template<typename...Args> void Write(int v, Args...args) { Write(args...); } template<typename...Args> void Write(char v, Args...args) { Write(args...); } template<typename...Args> void Write(const Test<char>&v, Args...args) { Write(args...); } }; int main() { Test<char> t; const Test<char>&t2=t; t.Write(4); // 编译正常 t.Write(t2);// 编译正常 t.Write(0, t2); // 编译报错 }
报错根本原因
该问题由C++模板的依赖名两阶段查找规则导致:
- 单独调用
t.Write(4)时,匹配接收int参数的Write重载,递归调用无参Write(),无参版本在int版本声明前就已定义,查找可见,因此编译正常。 - 单独调用
t.Write(t2)时,调用位置在main函数,此时Test类的所有Write重载都已完成声明,重载决议可以找到接收const Test<char>&的版本,因此编译正常。 - 调用
t.Write(0, t2)时,首先匹配到接收int的Write重载,该函数内部递归调用Write(args...)时,args的类型依赖模板参数包Args,因此此处的Write属于依赖名。按照两阶段查找规则,依赖名的非ADL查找只会在当前模板(即接收int的Write模板)定义的位置查找可见的函数声明:此时排在它后面的Write(const Test<char>&, Args...)重载还未被声明,ADL查找也无法定位类的成员函数,最终重载决议找不到匹配的版本,触发编译错误。
注:代码中删除非const拷贝构造的写法不影响本次报错,因为所有相关调用都是传const引用,不会触发拷贝构造逻辑。
可行解决方案
除拆分参数多次调用外,有以下几种低改动的修复方案:
- 方案1:修改递归调用的写法,将所有Write重载内部的递归调用
Write(args...)改为this->Write(args...)。加上this->后,调用变为明确的类成员访问形式,名字查找会延迟到模板实例化阶段执行,此时类的所有成员重载都已完成声明,可以正确匹配到接收const Test<char>&的版本,改动量最小。 - 方案2:调整类内Write重载的声明顺序,将接收
const Test<char>&的Write重载移动到所有基础类型(int/unsigned int/char)Write重载的前面,确保所有基础类型版本的Write在定义时,就能看到自定义类型的Write重载。 - 方案3:将所有Write重载的原型声明放在类内靠前位置,函数具体实现移到类外定义。类内所有声明完成后,类外定义时所有重载都处于可见状态,不会因声明顺序导致查找失败。
内容的提问来源于stack exchange,提问作者Cal
相关产品推荐
相关产品推荐

