【问题标题】:C++11: Overload fails to resolve recursive decltypeC++11:重载无法解析递归 decltype
【发布时间】:2014-03-15 20:23:37
【问题描述】:

在下面的代码中,我试图构建一个类型格。例如,在floatint 之间,将结果提升为float

float join(float f, int)   { return f; }
float join(float f, float) { return f; }

那我介绍一个wrapper类型:

template <typename Inner>
struct wrapper
{
  using inner_t = Inner;
  inner_t value;
};

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

它也可以是带有“标量”类型的joined:

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

到目前为止,一切都很好,它有效。但是,因为在实际情况中我实际上有更多这样的规则,所以我想避免重复规则的数量来表达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);
}

这适用于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.
}

叮当声:

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)
    ^

海合会:

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

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

【问题讨论】:

  • 不是你的join(wrapper, wrapper) = wrapper, join(wrapper, wrapper) = scalar 暧昧吗?

标签: c++ c++11 overloading decltype trailing-return-type


【解决方案1】:

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

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

这里join的名称查找不会通过非限定查找找到相同的函数模板,因为trailing-return-type是声明的一部分,只能找到名称一旦它们被宣布。但是语法允许 ADL 找到相同的函数模板。依赖名称的 ADL 稍后执行(从实例化点开始)。

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

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

演绎和替换在递归的任何一步中都不会失败,选择此重载的唯一原因是在称为join不同函数模板之间进行部分排序.部分排序仅作为重载解决过程的一部分进行检查——这为时已晚,无法阻止进一步的实例化。

如错误消息中所述,此错误是作为实施限制发生的。因此,它不属于 SFINAE 类别,请参阅Solving the SFINAE problem for expressions。因此,即使不选择这个重载,它的存在也会使程序格式错误,就像

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

【讨论】:

  • 所以我的理解是我的代码的主要问题是我没有拼出命名空间?这可以很容易地解决,并且似乎可以按我的预期工作:coliru.stacked-crooked.com/a/47378b37eb430b62。有没有我可能忽略的陷阱?
  • @akim 当只依赖非限定查找时,您将无法将wrapper&lt;wrapper&lt;int&gt;&gt; 之类的东西与任何东西结合起来。
  • 我猜你的意思是“合格的查找”。 Bummer :( 感谢您的帮助!现在我需要了解如何找到正确的实现 :)
  • @dyp:但是当混合两者时,(和正确的顺序),我们可以joinwrapper&lt;wrapper&lt;float&gt;&gt;Demo。 :)
  • @Jarod42 这是一个聪明的解决方法,但可能有点太聪明了;)this 怎么样?
【解决方案2】:

定义join_traits 模板。专门用于标量和包装器。使用

join_traits<T1>::type
join_traits<T2>::type

和/或

join_traits<T1>::get_value(v1)
join_traits<T2>::get_value(v2)

在您的单一功能模板中

template<class T1, class T2>
auto join (T1 v1, T2 v2) -> ...

【讨论】:

  • 我认为这个答案可以更多地解释如何以及为什么可以用来解决问题。目前,我认为它只会帮助那些可能自己想出它的人。
  • @Agentlien:至于“如何”,真的有两种不同的方法吗?至于“为什么”,我认为很明显,您定义了 N 个特征和一个函数模板,而不是 N^2 之类的。
  • 重新表述:您的答案仅声明模板类型的存在、其两个潜在成员的名称和函数的部分原型(跳过一个重要细节)。我同意这是一个很好的解决方案,并且没有很多明智的实施方式。但是,我仍然认为您至少可以负担得起编写 join 函数的完整实现并解释它如何解决问题的费用。正如我在上一条评论中提到的那样:目前,我认为这个答案无法帮助大多数需要帮助来回答这个问题的人。
  • @Agentlien 也许你是对的,等我有空我会扩大答案。
猜你喜欢
  • 2018-09-30
  • 2012-08-11
  • 1970-01-01
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 2021-01-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多