隐式实例化未定义的模板'class' [英] implicit instantiation of undefined template 'class'

查看:335
本文介绍了隐式实例化未定义的模板'class'的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当试图为我的库中的const和非const模板参数提供函数时,我遇到了一个奇怪的问题。以下源代码是一个极少的示例现象:

  #include< iostream> 


模板< typename some_type>
struct some_meta_class;

模板<>
struct some_meta_class< int>
{
typedef void type;
};



模板< typename some_type>
struct return_type
{
typedef typename some_meta_class< some_type> :: type test;

typedef void type;
};



模板<类型名称类型>
typename return_type< type> :: type foo(type& in)
{
std :: cout<< 非const<<的std :: ENDL;
}

模板< typename type>
void foo(类型为const& in)
{
std :: cout<< const<<的std :: ENDL;
}


int main()
{
int i;

int const& ciref = i;
foo(ciref);
}

我尝试为foo实现一个非const版本和一个const版本,但不幸的是,这个代码不会在CLANG 3.0和gcc 4.6.3上编译。


main.cpp:18:22:error:implicit instantiation未定义的模板
'some_meta_class'


因此,出于某种原因,编译器希望使用非常量版本的foo一个const int参考。这显然会导致上述错误,因为some_meta_class没有实现。奇怪的是,如果你做了以下变化之一,代码编译得很好,并能正常工作:




  • 取消注释/ const版本
  • uncomemnt /删除return_type :: test的typedef


这个例子是课程简约而纯粹的学术。在我的库中,我遇到了这个问题,因为const和非const版本返回不同的类型。我通过使用部分专用的助手类来解决了这个问题。



但是为什么上面的例子会导致这种奇怪的行为?为什么编译器不想使用非const版本,其中const版本是有效的并且匹配得更好?解析方案

解析方案

原因在于执行函数调用解析的方式以及模板参数推导和替换。


  1. 首先,执行名称查找。这为您提供了两个匹配名称 foo()的函数。其次,执行类型演绎:为模板函数中的每个匹配名称,编译器试图推导出可以产生可行匹配的函数模板参数。在这个阶段发生错误。

  2. 第三,重载分辨率进入游戏。这只是在 类型推导被执行之后,并且已经确定了用于解决调用的可行函数的签名,这是有道理的:编译器只有在找到

事实上,你得到一个与非常量重载相关的错误不是因为编译器选择它作为解决调用的最可行候选项(这将是第3步),而是因为编译器在实例化其返回类型以确定其签名时在步骤2中产生错误。



不完全明显为什么这会导致错误,因为人们可能会认为应用 SFINAE (替换失败不是错误) 。为了澄清这一点,我们可以考虑一个更简单的例子:

  template< typename T> struct X {}; 

模板< 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); //选择重载2
}

在本例中,SFINAE适用:在步骤2中,编译器会为上述两个重载中的每一个推导出 T ,并尝试确定它们的签名。在过载1的情况下,这会导致替换失败 X < / code>没有定义任何 type (no typedef X 中)。但是,由于SFINAE,编译器只是简单地丢弃它,并发现过载2是可行的匹配。因此,它会选择它。



现在让我们稍微改变示例,以反映您的示例的方式:

  template< typename T> struct X {}; 

模板< typename Y>
struct R {typedef typename X< Y> :: type type; };

//注意从X< T>进入R T!
模板< 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); //错误!无法实例化R< int const>



$ b $ p
$ b

改变的是重载1不再返回 X< T> :: type ,而是 R< T> :: type 。由于 typedef 与< X :: type >声明在 R 中,所以人们可能会期望它产生相同的结果。但是,在这种情况下,您会收到编译错误。为什么?

标准有答案(第14.8.3 / 8段):


如果替换导致无效类型或表达式,则键入演绎失败。无效的类型或表达式是使用替代参数编写的格式不正确的类型或表达式。 [...]只有函数类型的直接上下文中的无效类型和表达式及其模板参数类型可能导致扣除失败。


显然,第二个示例(以及您的)在嵌套上下文中生成错误,因此SFINAE不适用。我相信这会回答你的问题。顺便提一下,有趣的是,这个自从C ++ 03 发生了变化,这是更一般地说(第14.8.2 / 2段):


[...]如果在模板参数或函数中进行替换函数模板的类型导致无效类型,类型演绎失败。 [b]

如果您对原因的原因感到好奇, 本文 可能会给你一个想法。


When trying to offer functions for const and non-const template arguments in my library I came across a strange problem. The following source code is a minimal example phenomenon:

#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);
}

I tried to implement a non-const version and a const version for foo but unfortunately this code won't compile on CLANG 3.0 and gcc 4.6.3.

main.cpp:18:22: error: implicit instantiation of undefined template 'some_meta_class'

So for some reason the compiler wants to use the non-const version of foo for a const int-reference. This obviously leads to the error above because there is no implementation for some_meta_class. The strange thing is, that if you do one of the following changes, the code compile well and works:

  • uncomment/remove the non-const version
  • uncomemnt/remove the typedef of return_type::test

This example is of course minimalistic and pure academic. In my library I came across this problem because the const and non-const version return different types. I managed this problem by using a helper class which is partially specialized.

But why does the example above result in such strange behaviour? Why doesn't the compiler want to use the non-const version where the const version is valid and and matches better?

解决方案

The reason is the way function call resolution is performed, together with template argument deduction and substitution.

  1. Firstly, name lookup is performed. This gives you two functions with a matching name foo().

  2. Secondly, type deduction is performed: for each of the template functions with a matching name, the compiler tries to deduce the function template arguments which would yield a viable match. The error you get happens in this phase.

  3. Thirdly, overload resolution enters the game. This is only after type deduction has been performed and the signatures of the viable functions for resolving the call have been determined, which makes sense: the compiler can meaningfully resolve your function call only after it has found out the exact signature of all the candidates.

The fact that you get an error related to the non-const overload is not because the compiler chooses it as a most viable candidate for resolving the call (that would be step 3), but because the compiler produces an error while instantiating its return type to determine its signature, during step 2.

It is not entirely obvious why this results in an error though, because one might expect that SFINAE applies (Substitution Failure Is Not An Error). To clarify this, we might consider a simpler example:

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
}

In this example, SFINAE applies: during step 2, the compiler will deduce T for each of the two overloads above, and try to determine their signatures. In case of overload 1, this results in a substitution failure: X<const int> does not define any type (no typedef in X). However, due to SFINAE, the compiler simply discards it and finds that overload 2 is a viable match. Thus, it picks it.

Let's now change the example slightly in a way that mirrors your example:

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>
}

What has changed is that overload 1 no longer returns X<T>::type, but rather R<T>::type. This is in turn the same as X<T>::type because of the typedef declaration in R, so one might expect it to yield the same result. However, in this case you get a compilation error. Why?

The Standard has the answer (Paragraph 14.8.3/8):

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed if written using the substituted arguments. [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

Clearly, the second example (as well as yours) generates an error in a nested context, so SFINAE does not apply. I believe this answers your question.

By the way, it is interesting to notice, that this has changed since C++03, which more generally stated (Paragraph 14.8.2/2):

[...] If a substitution in a template parameter or in the function type of the function template results in an invalid type, type deduction fails. [...]

If you are curious about the reasons why things have changed, this paper might give you an idea.

这篇关于隐式实例化未定义的模板'class'的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆