递归返回类型的名称解析 [英] Name resolution for recursive trailing return type

查看:113
本文介绍了递归返回类型的名称解析的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



在下面的代码中,我们在一个整数和一个iter函数中定义一个模板结构,将此类型的一个对象作为参数。返回类型取决于在递减模板值之后调用自身的结果。



为了打破实例化循环(或者我想),我提供了一个返回非依赖类型的特殊化。



我们有一个玩具主要实例化模板。



这里是一些代码:

  template< int i> struct Int {}; 

constexpr auto iter(Int< 0>) - > Int< 0>

template< int i> constexpr auto iter(Int< i>) - > decltype(iter(Int< i-1> {}));

int main(){
decltype(iter(Int< 10> {}))
}

此代码在gcc 4.9和clang 3.5中都不起作用。两者都触发无限实例化(它们与专门的基础案例不匹配)。

  rec.cpp:11:62:fatal error :递归模板实例化超过最大深度256 
template< int i> constexpr auto iter(Int< i>) - > decltype(iter(Int< i-1> {}));

现在,如果我们使用C ++ 14 decltype(auto) code>,我们为模板提供了一个返回完全相同的东西的主体:

  template< int i& struct Int {}; 

constexpr auto iter(Int< 0>) - > Int< 0>

template< int i>
constexpr auto iter(Int< i>) - > decltype(auto){
return iter(Int< i-1> {});
}

int main(){
decltype(iter(Int< 10&
}

现在这两个编译器都可以正常工作。



我尝试了不同的方式来表达专业化,并将它移动一点(注意它的位置),但这并不妨碍它的自焚;(



我也试着用更多 decltype declval 我似乎不能得到C ++ 11的语法工作。



有人可以解释两种语法之间的名称查找的区别吗?

解决方案

这是因为重载解析,模板重载解析,模板声明实例化和模板定义实例化的相对顺序。当编译器需要评估 decltype(iter(Int< 0> {}))时,我们来看看C ++ 11的情况。



,它对以参数prvalue Int <0> 调用的名称 iter 执行重载解析。由于模板是在过载集中,因此我们应用14.8.3 [temp.over]


1 - 函数模板可以通过其名称的(非模板)函数或(其他)相同名称的函数模板重载。当调用该名称时(显式地或隐式地使用运算符符号),对每个函数模板执行模板参数推导(14.8.2)和检查任何显式模板参数(14.3)以找到模板参数值如果有的话),可以与
一起使用函数模板来实例化可以使用调用参数调用的函数模板特殊化。 [...]


结果,声明 template< int i& constexpr auto iter(...) - > ... i = 0 实例化(<14.7.1p10 [temp.inst] 评估 decltype(iter(Int <-1> {}))并关闭我们去的负整数。



不需要 constexpr auto iter(Int< 0>) - > Int< 0> 将是更好的重载(通过13.3.3p1 [over.match.best] ),编译器正在快速前进到负无穷大。



相反,使用C ++ 14推导出的返回类型7.1.6.4p12 [dcl.spec.auto] 应用:


12 - 在声明类型中具有占位符的函数模板的返回类型扣除发生在定义被实例化时[...]


由于模板重载解析(14.7.1p3)之后坏模板 iter< 0> 从不实例化; 14.8.3p5:


5 - 只需要函数模板特殊化的签名,即可在一组候选函数中输入特化。因此,只需要函数模板声明来解析模板特化是候选对象的调用。


code> iter< 0> 这里是(Int< 0>) - decltype(auto),包含占位符类型(7.1.6.4)的签名。






建议的解决方法:使用SFINAE阻止对 iter(Int <-1> {})的任何尝试调用:

 模板< int i> constexpr auto iter(Int< i>)
- > decltype(iter(typename std :: enable_if< i!= 0,int< i-1> :: type {}));
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
decltype c>,确实在 iter 的调用内。


I found a weird difference between explicit and automatic trailing return types.

In the following code, we define a struct templated on an integer and an iter function, which take one object of this type as argument. The return type depends on the result of calling itself after decrementing the template value.

To break the instantiation loop (or so I thought), I provide a specialization which returns a non-dependent type.

We have a toy main to instantiate the templates.

Here is a bit of code:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));

int main(){
  decltype(iter(Int<10>{})) a;
}

This code does not work in both gcc 4.9 and clang 3.5. Both trigger infinite instantiation (they don't match the specialized base case).

rec.cpp:11:62: fatal error: recursive template instantiation exceeded maximum depth of 256
template<int i> constexpr auto iter(Int<i>) -> decltype(iter(Int<i-1>{}));

Now, if we use C++14 decltype(auto) and we provide a body for the template which returns the exact same thing:

template<int i> struct Int {};

constexpr auto iter(Int<0>) -> Int<0>;

template<int i>
constexpr auto iter(Int<i>) -> decltype(auto) {
  return iter(Int<i-1>{});
}

int main(){
  decltype(iter(Int<10>{})) a;
}

This now works for both compilers and behave as expected.

I tried different ways to express the specialization and moved it around a bit (to be careful about its location), but that didn't prevent its self-immolation ;(

I also tried to sprinkle the code with more decltype and declval, but I can't seem to get the C++11 syntax working.

Could someone explain the difference between the two syntaxes for the name lookup?

解决方案

It's because of the relative ordering of overload resolution, template overload resolution, template declaration instantiation, and template definition instantiation.

Let's look at the C++11 case first. When the compiler needs to evaluate decltype(iter(Int<0>{})), it performs overload resolution on the name iter called with arguments prvalue Int<0>. Since a template is in the overload set, we apply 14.8.3 [temp.over]:

1 - A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name. When a call to that name is written (explicitly, or implicitly using the operator notation), template argument deduction (14.8.2) and checking of any explicit template arguments (14.3) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments. [...]

As a result, the declaration template<int i> constexpr auto iter(...) -> ... is instantiated (14.7.1p10 [temp.inst]) with i = 0, which forces the evaluation of decltype(iter(Int<-1>{})) and off down the rabbit hole of negative integers we go.

It doesn't matter that constexpr auto iter(Int<0>) -> Int<0> would be a better overload (by 13.3.3p1 [over.match.best]), because we never get that far; the compiler is away marching merrily towards negative infinity.

By contrast, with C++14 deduced return types 7.1.6.4p12 [dcl.spec.auto] applies:

12 - Return type deduction for a function template with a placeholder in its declared type occurs when the definition is instantiated [...]

Since definition instantiation occurs after template overload resolution (14.7.1p3), the bad template iter<0> is never instantiated; 14.8.3p5:

5 - Only the signature of a function template specialization is needed to enter the specialization in a set of candidate functions. Therefore only the function template declaration is needed to resolve a call for which a template specialization is a candidate.

The "signature" of iter<0> here is (Int<0>) -> decltype(auto), a signature containing a placeholder type (7.1.6.4).


Suggested workaround: use SFINAE to prevent any attempted call to iter(Int<-1>{}):

template<int i> constexpr auto iter(Int<i>)
  -> decltype(iter(typename std::enable_if<i != 0, Int<i-1>>::type{}));
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        ^^^^^^^

Note that the SFINAE has to go inside the decltype, and indeed inside the call to iter.

这篇关于递归返回类型的名称解析的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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