C++ 11:重载无法解析递归的decltype

aki*_*kim 10 c++ overloading decltype c++11 trailing-return-type

在下面的代码中,我正在尝试构建一个类型的网格.例如,在float和之间int,将结果推广到float:

float join(float f, int)   { return f; }
float join(float f, float) { return f; }
Run Code Online (Sandbox Code Playgroud)

然后我介绍一种wrapper类型:

template <typename Inner>
struct wrapper
{
  using inner_t = Inner;
  inner_t value;
};
Run Code Online (Sandbox Code Playgroud)

join操作行为很自然:

template <typename Inner1, typename Inner2>
auto
join(const wrapper<Inner1>& w1, const wrapper<Inner2>& w2)
  -> wrapper<decltype(join(w1.value, w2.value))>
{
  return {join(w1.value, w2.value)};
}
Run Code Online (Sandbox Code Playgroud)

它也可以join用"标量"类型编辑:

template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2)
  -> wrapper<decltype(join(w1.value, value2))>
{
  return {join(w1.value, value2)};
}
Run Code Online (Sandbox Code Playgroud)

到目前为止,这么好,它的工作原理.但是,因为在实际情况下我实际上有更多这样的规则,我想避免重复表达join操作交换性的规则数量,因此,我表达了join(scalar, wrapper) := join(wrapper, scalar)(事实上​​,我更喜欢某些东西)比如join(v1, v2) := join(v2, v1),但让我们从更具体的东西开始.):

template <typename T1, typename Inner2>
auto
join(const T1& value1, const wrapper<Inner2>& w2)
  -> decltype(join(w2, value1))
{
  return join(w2, value1);
}
Run Code Online (Sandbox Code Playgroud)

这适用于join(scalar, scalar),join(wrapper, scalar)join(scalar, wrapper).但随后join(wrapper, wrapper)导致模板函数无限扩展,包括G ++ 4.9和Clang ++ 3.5,我不明白.

int main()
{
  int i;
  float f;
  wrapper<float> fr;
  join(f, i);
  join(fr, i);
  join(i, fr);
  join(fr, fr); // Loops.
}
Run Code Online (Sandbox Code Playgroud)

铛:

clang++-mp-3.5 -std=c++11 bar.cc
bar.cc:21:5: fatal error: recursive template instantiation exceeded maximum depth of
      256
    join(const wrapper<Inner1>& w1, const T2& value2)
    ^
bar.cc:29:5: note: while substituting deduced template arguments into function
      template 'join' [with T1 = wrapper<float>, Inner2 = float]
    join(const T1& value1, const wrapper<Inner2>& w2)
    ^
Run Code Online (Sandbox Code Playgroud)

GCC:

g++-mp-4.9 -std=c++11 bar.cc
bar.cc:30:34: error: template instantiation depth exceeds maximum of 900 (use -ftemplate-depth= to increase the maximum) substituting 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = <missing>; Inner2 = <missing>]'
       -> decltype(join(w2, value1))
                                  ^
bar.cc:30:34:   recursively required by substitution of 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = wrapper<float>; Inner2 = float]'
bar.cc:30:34:   required by substitution of 'template<class T1, class Inner2> decltype (join(w2, value1)) join(const T1&, const wrapper<Inner2>&) [with T1 = wrapper<float>; Inner2 = float]'
bar.cc:43:18:   required from here
Run Code Online (Sandbox Code Playgroud)

我不明白为什么重载不会减少递归.到底是怎么回事?可能有一个可能的替代实现与(类)模板专业化,但我不是在寻找替代实现:我想了解这个有什么问题.提前致谢.

dyp*_*dyp 4

这有几个问题,其中之一会导致错误。

template <typename Inner1, typename T2>
auto
join(const wrapper<Inner1>& w1, const T2& value2)  // (A)
  -> wrapper<decltype(join(w1.value, value2))>;
Run Code Online (Sandbox Code Playgroud)

这里的名称查找join不会通过非限定查找找到相同的函数模板,因为Trailing-return-type是声明的一部分,并且名称只有声明才能找到。但语法允许 ADL 找到相同的函数模板。从属名称的 ADL 稍后执行(从实例化点开始)。

据我了解这个问题,问题来自重载解析:在decltype(join(w1.value, value2))尝试解析重载之前,需要实例化具有该名称的所有函数模板。对于每个函数模板,都会将一个实例化添加到重载集中(如果实例化成功)。

因此,所有的joins都需要实例化。实例化包括确定返回类型。对于此特定join函数模板 (A) 的每个实例化,具有相同模板参数的相同函数模板 (A) 是重载决策集的候选者。也就是说,要确定(A)有哪种返回类型,需要有一个重载决策,这需要确定(A)的返回类型等等。

推导和替换在递归的任何一步中都不会失败,选择此重载的唯一原因是调用的不同函数模板之间的部分排序join并且部分排序仅作为重载解析过程的一部分进行检查——这已经来不及阻止进一步的实例化了。

正如错误消息中提到的,此错误是作为实现限制而发生的。因此,它不属于 SFINAE 类别,请参阅解决表达式的 SFINAE 问题。因此,即使不选择此重载,它的存在也会使程序格式错误,就像

struct tag_for_ADL {};

template<class T>
auto foo(T p) -> decltype(foo(p));

foo(tag_for_ADL{}); // ill-formed, leads to infinite recursive instantiation
Run Code Online (Sandbox Code Playgroud)