G++下模板函数f的重载歧义问题:看似无歧义却报错
G++ 8.4/9.4下模板函数重载歧义问题解析
问题概述
构造了一个最简C代码示例,其中G 8.4和9.4版本会对函数f触发重载歧义编译错误,但该重载逻辑上并无歧义;而MSVC与Clang编译器均可正常编译该代码,并选择正确的重载版本。
编译错误信息
<source>:45:25: error: call of overloaded 'f(std::tuple<select_type<int, int>, select_type<double, char> &>, select_type<int, variadic_type<variadic_entry<int>, variadic_index<0, 0> > >&)' is ambiguous 45 | auto f1 = f(tupl, v1); // g++ says ambiguous? | ^ <source>:28:6: note: candidate: 'auto f(std::tuple<select_type<T1s, T2s>...>, const select_type<T0, variadic_type<variadic_entry<T10>, T11> >&) [with T1s = {int, double}; T2s = {int, char}; T0 = int; T10 = int; T11 = variadic_index<0, 0>]' 28 | auto f(std::tuple<select_type<T1s, T2s>...>, select_type<T0, variadic_type<variadic_entry<T10>, T11>> const&) | ^ <source>:34:6: note: candidate: 'auto f(std::tuple<select_type<T1s, T2s>...>, const select_type<T0, variadic_type<variadic_entry<T10>, variadic_index<I10, I11> > >&) [with T1s = {int, double}; T2s = {int, char}; T0 = int; T10 = int; int I10 = 0; int I11 = 0]' 34 | auto f(std::tuple<select_type<T1s, T2s>...>, select_type<T0, variadic_type<variadic_entry<T10>, variadic_index<I10, I11>>> const&) | ^
完整代码
#include <tuple> template<typename... Ts> struct variadic_type {}; template<typename T> struct variadic_entry {}; template<int I0, int I1> struct variadic_index {}; template<typename T0, typename T1> struct select_type {}; template<typename T0, typename T10, typename T11> struct select_type<T0, variadic_type<variadic_entry<T10>, T11>> { select_type(T0 = {}, variadic_type<variadic_entry<T10>, T11> = {}) {} }; template<typename T0, typename T10, int I10, int I11> struct select_type<T0, variadic_type<variadic_entry<T10>, variadic_index<I10, I11>>> { select_type(T0 = {}, variadic_type<variadic_entry<T10>, variadic_index<I10, I11>> = {}) {} }; template<typename... T1s, typename... T2s, typename T0, typename T10, typename T11> auto f(std::tuple<select_type<T1s, T2s>...>, select_type<T0, variadic_type<variadic_entry<T10>, T11>> const&) { return 0; } template<typename... T1s, typename... T2s, typename T0, typename T10, int I10, int I11> auto f(std::tuple<select_type<T1s, T2s>...>, select_type<T0, variadic_type<variadic_entry<T10>, variadic_index<I10, I11>>> const&) { return 1; } int main() { auto tupl = std::make_tuple(select_type<int, int>{}, select_type<double, char>{}); auto v0 = select_type<int, variadic_type<variadic_entry<int>, variadic_entry<void>>>{}; auto v1 = select_type<int, variadic_type<variadic_entry<int>, variadic_index<0, 0>>>{}; auto f0 = f(tupl, v0); auto f1 = f(tupl, v1); // g++ says ambiguous? }
特殊现象
- 若将tuple参数改为
std::tuple<Ts...>而非std::tuple<select_type<T1s, T2s>...>,则无歧义,可选择正确重载; - 若移除tuple参数,同样无歧义。
问题解释
这是G++ 8.4/9.4版本在模板参数推导与重载决议阶段的bug。
按照C++标准,两个f函数重载中,第二个重载的第二个参数select_type<T0, variadic_type<variadic_entry<T10>, variadic_index<I10, I11>>>是第一个重载对应参数select_type<T0, variadic_type<variadic_entry<T10>, T11>>的更特化版本——因为variadic_index<I10,I11>是模板参数T11的具体实例化类型,特化程度更高。重载决议应当优先选择更特化的模板。
但G++旧版本在同时处理第一个参数的可变参数推导(T1s...和T2s...的展开匹配)与第二个参数的特化优先级判断时,出现逻辑错误,未能正确识别第二个重载的更特化属性,导致认为两个重载的匹配程度完全相同,触发歧义错误。
当简化第一个参数的推导逻辑(改为std::tuple<Ts...>)或移除tuple参数后,模板推导过程变得简单,G++能正确处理第二个参数的特化优先级,因此不会触发歧义。
内容的提问来源于stack exchange,提问作者Riddick
相关产品推荐
相关产品推荐

