可变参数元组的重载方法解析出现异常结果
C++可变参数元组模板函数重载解析问题解析
问题代码
#include <string> #include <iostream> #include <tuple> using namespace std; struct Foo { template < typename ... t_Tys > static void foo( tuple< t_Tys ... > const & ) { cerr << "foo()\n"; } template < class ... t_Tys > static void foo( tuple< string, t_Tys ... > const & _rtp ) { cout << "foo( string )\n"; } template < class ... t_Tys > static void foo( tuple< string, int, t_Tys ... > const & _rtp ) { cout << "foo( string, int ) [" << get<1>(_rtp) << "]\n"; } }; int main() { Foo::foo( make_tuple<string>( "foo" ) ); Foo::foo( make_tuple<int>( 1 ) ); Foo::foo( make_tuple( "foo", 1 ) ); Foo::foo( make_tuple<string,int>( "foo", 1 ) ); }
初始输出
foo() foo() foo( string ) foo( string, int ) [1]
修改后的调用与输出
将第一个调用改为Foo::foo( make_tuple( "foo" ) );后,输出变为:
foo() foo() foo() foo( string, int ) [1]
- 问题1:为何第一个调用
Foo::foo(make_tuple<string>("foo"))匹配的是通用foo()而非接受tuple<string>的重载版本?
当传入tuple<string>时,两个重载都能匹配:- 通用版本推导
t_Tys...为[string],实例化为void foo(tuple<string> const&); - 第二个重载推导
t_Tys...为空参数包,实例化为void foo(tuple<string> const&)。
- 通用版本推导
此时两个模板实例化后的函数签名完全一致。根据C++重载解析规则,当两个模板的特化程度无法区分(空参数包让tuple<string, t_Tys...>退化为与通用版本完全相同的参数类型),编译器会选择先定义的重载,也就是通用版本。
问题2:为何修改后的第一个调用
Foo::foo(make_tuple("foo"))仍未匹配接受tuple<string>的重载版本?make_tuple("foo")的推导类型是tuple<const char*>,而非tuple<string>。第二个重载要求实参tuple的第一个元素必须是string类型,但const char*与string类型不匹配,导致模板参数推导失败。此时只有通用版本可行,因此调用通用foo()。问题3:为何修改第一个调用会导致第三个调用的重载选择发生变化?
这源于C++模板重载的偏序规则依赖全局推导上下文:- 初始状态下,第三个调用的实参是
tuple<const char*, int>,编译器允许将其隐式转换为tuple<string, int>(每个元素分别转换),此时第二个重载因更特化被优先选择; - 修改第一个调用后,编译器在分析模板特化程度时,会纳入第一个调用的推导失败记录(
tuple<const char*>无法匹配第二个重载),重新判定第二个重载的适用范围。在第三个调用场景下,由于实参类型与第二个重载的模板参数无法直接推导(需隐式转换),编译器不再将第二个重载视为更优选项,最终回退到通用版本。
- 初始状态下,第三个调用的实参是
内容的提问来源于stack exchange,提问作者David Bien
相关产品推荐
相关产品推荐

