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

时间:2014-02-16 16:18:14

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

在下面的代码中,我正在尝试构建一个类型的网格。例如,在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)};
}

也可以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)};
}

到目前为止,这么好,它有效。但是,因为在实际情况下我实际上有更多这样的规则,我想避免重复表达join操作的交换性的规则数量,因此,我表达{{1} (事实上​​,我更喜欢像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)导致模板函数无限扩展,包括G ++ 4.9和Clang ++ 3.5,我不明白。

join(wrapper, wrapper)

锵:

int main()
{
  int i;
  float f;
  wrapper<float> fr;
  join(f, i);
  join(fr, i);
  join(i, fr);
  join(fr, fr); // Loops.
}

GCC

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

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

2 个答案:

答案 0 :(得分:4)

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

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))尝试解析为重载之前,需要实例化具有该名称的所有函数模板。对于每个函数模板,将一个实例添加到重载集(如果实例化成功)。

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

扣除&amp;在递归的任何一个步骤中,替换永远不会失败,选择此重载的唯一原因是名为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

答案 1 :(得分:0)

定义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) -> ...