Flo*_*olf 12 c++ templates overloading const function
当我试图在我的库中为const和非const模板参数提供函数时,我遇到了一个奇怪的问题.以下源代码是一个最小的示例现象:
#include <iostream>
template<typename some_type>
struct some_meta_class;
template<>
struct some_meta_class<int>
{
typedef void type;
};
template<typename some_type>
struct return_type
{
typedef typename some_meta_class< some_type >::type test;
typedef void type;
};
template<typename type>
typename return_type<type>::type foo( type & in )
{
std::cout << "non-const" << std::endl;
}
template<typename type>
void foo( type const & in )
{
std::cout << "const" << std::endl;
}
int main()
{
int i;
int const & ciref = i;
foo(ciref);
}
Run Code Online (Sandbox Code Playgroud)
我试图为foo实现一个非const版本和一个const版本,但不幸的是这个代码不能在CLANG 3.0和gcc 4.6.3上编译.
main.cpp:18:22:错误:未定义模板'some_meta_class'的隐式实例化
因此,由于某种原因,编译器想要使用非const版本的foo作为const int-reference.这显然会导致上面的错误,因为some_meta_class没有实现.奇怪的是,如果您执行以下更改之一,代码编译良好并且有效:
这个例子当然是简约和纯粹的学术.在我的库中,我遇到了这个问题,因为const和非const版本返回不同的类型.我通过使用部分专用的辅助类来解决这个问题.
但为什么上面的例子导致了这种奇怪的行为呢?为什么编译器不想使用const版本有效且非匹配的非const版本?
And*_*owl 21
原因是执行函数调用解析的方式,以及模板参数推导和替换.
首先,执行名称查找.这为您提供了两个具有匹配名称的函数foo().
其次,执行类型推导:对于具有匹配名称的每个模板函数,编译器尝试推导出将产生可行匹配的函数模板参数.你得到的错误发生在这个阶段.
第三,重载决议进入游戏.这只是在执行了类型推导并且已经确定了用于解析调用的可行函数的签名之后,这是有道理的:只有在找到所有候选的确切签名之后,编译器才能有意义地解析函数调用.
您得到与非const重载相关的错误的事实不是因为编译器选择它作为解析调用的最可行的候选者(这将是步骤3),而是因为编译器在实例化其返回类型时产生错误在步骤2中确定其签名.
这是不完全明显的,为什么这会导致一个错误,但因为人们可能会想到,SFINAE应用(替换失败不是一个错误).为了澄清这一点,我们可以考虑一个更简单的例子:
template<typename T> struct X { };
template<typename T> typename X<T>::type f(T&) { } // 1
template<typename T> void f(T const&) { } // 2
int main()
{
int const i = 0;
f(i); // Selects overload 2
}
Run Code Online (Sandbox Code Playgroud)
在此示例中,SFINAE适用:在步骤2中,编译器将推导出T上述两个重载中的每一个,并尝试确定其签名.在过载1的情况下,这导致了替换失败:X<const int>没有定义任何type(无typedef中X).但是,由于SFINAE,编译器只是丢弃它并发现重载2是可行的匹配.因此,它选择它.
现在让我们以一种反映你的例子的方式稍微改变一下这个例子:
template<typename T> struct X { };
template<typename Y>
struct R { typedef typename X<Y>::type type; };
// Notice the small change from X<T> into R<T>!
template<typename T> typename R<T>::type f(T&) { } // 1
template<typename T> void f(T const&) { } // 2
int main()
{
int const i = 0;
f(i); // ERROR! Cannot instantiate R<int const>
}
Run Code Online (Sandbox Code Playgroud)
改变的是超载1不再返回X<T>::type,而是R<T>::type.这又与同为X<T>::type因的typedef在声明中R,所以人们可能会期望它产生相同的结果.但是,在这种情况下,您会收到编译错误.为什么?
标准有答案(第14.8.3/8段):
如果替换导致无效的类型或表达式,则类型推导失败.如果使用替换参数写入,则无效的类型或表达式将是格式错误的.[...]只有函数类型的直接上下文中的无效类型和表达式及其模板参数类型才会导致演绎失败.
显然,第二个示例(以及您的示例)在嵌套上下文中生成错误,因此SFINAE不适用.我相信这回答了你的问题.
顺便说一下,有趣的是,自从C++ 03以来,这已经发生了变化,这更加普遍地说明了(第14.8.2/2段):
[...]如果模板参数或函数模板的函数类型中的替换导致类型无效,则类型推导失败.[...]
如果您想了解的原因,为什么事情发生了变化,本文可能会给你的想法.