可变参数模板递归返回类型推导编译错误

时间:2015-05-07 14:11:45

标签: c++ c++11 variadic-templates return-type-deduction

为什么以下代码无法编译?

template <typename T>
T sum(T t){
    return t;
}

template <typename T, typename ...U>
auto sum(T t, U... u) -> decltype(t + sum(u...)) {
    return t + sum(u...);
}

int main() {
    sum(1, 1.5, 2);
}

编译错误:

error: no matching function for call to ‘sum(int, double, int)’
 sum(1, 1.5, 2);

实现此功能的好方法是什么?

3 个答案:

答案 0 :(得分:4)

这里我们将工作转发给助手类型:

namespace details {
  template<class...Ts>
  struct sum_t {};

  template<class T>
  struct sum_t<T> {
    T operator()(T t)const{ return std::forward<T>(t); }
  };

  template<class T, class...Ts>
  struct sum_t<T,Ts...> {
    auto operator()(T t, Ts...ts)const
    -> decltype( std::declval<T>() + sum_t<Ts...>{}(std::declval<Ts>()...) )
    {
      return std::forward<T>(t) + sum_t<Ts...>{}(std::forward<Ts>(ts)...);
    }
  };
}

template<class...Ts>
auto sum(Ts...ts)
-> decltype( details::sum_t<Ts...>{}(std::declval<Ts>()...) )
// -> std::result_of_t<details::sum_t<Ts...>(Ts...)>
// above line is C++14 and cleaner version of previous line
{
  return details::sum_t<Ts...>{}(std::forward<Ts>(ts)...);
}

基本问题是模板函数在-> decltype子句中计算自己的返回类型时无法看到自己。

有一些解决方法。上面应该可以工作,因为模板类可以在自己的主体中看到其部分特化的其他特化。另一种方法是使用Koenig查找(ADL)来推迟搜索其递归调用,直到它可以找到它自己的实例化点。我发现第二种方法更令人困惑。

如果我要编写自己的sum用于制作,我可以选择使用我期望它返回的类型,如果是,它会接受零长度总和(创建默认值)如果我传递了1个或多个参数,则不要求该类型是默认构造的。但我喜欢过度设计的通用代码:

template<class R0=void,class...Ts,class R=std::conditional_t<
  !std::is_same<R0,void>{},
  R0,
  std::result_of_t<details::sum_t<Ts...>(Ts...)>
>>
R sum(Ts...ts)
{
  return details::sum_t<R, Ts...>{}(std::forward<Ts>(ts)...);
}

我修改sum_t以将返回类型作为第一个参数:

namespace details {
  template<class R,class...Ts>
  struct sum_t {
    R operator()()const{ return {}; }
  };

  template<class R, class T>
  struct sum_t<R, T> {
    using R0 = std::conditional_t<!std::is_same<R,void>{},R,T>;
    R0 operator()(T t)const{ return std::forward<T>(t); }
  };

  template<class R, class T, class...Ts>
  struct sum_t<R, T,Ts...> {
    using R0 = std::conditional_t<
      !std::is_same<R,void>{},
      R,
      decltype( std::declval<T>() + sum_t<void,Ts...>{}(std::declval<Ts>()...) )
    >;
    R0 operator()(T t, Ts...ts)const
    {
      return std::forward<T>(t) + sum_t<void,Ts...>{}(std::forward<Ts>(ts)...);
    }
  };
}

这使得我希望能够写出&#34;做这个总和,但在继续&#34;之前将每个子总和投射到R。或某些。

在C ++ 1z中,您将要使用fold-expression。能够设置R仍然有用,就像您要添加表达式模板一样,它可能仅在当前范围结束之前作为表达式模板有效。

要在C ++ 14中修复此问题,您可能必须使用R返回值的延续传递样式。

然后我们可以将返回类型扣除折叠到游戏中以允许

Matrix m = sum( many_matrices... );

在Eigen工作(例如)。

当你第一次开始编写通用代码时,你必须问自己&#34;兔子洞的深度我们想要去哪里?&#34;

答案 1 :(得分:3)

为了完整性,因为在a similar question的这个版本上,Yakk发布了我在另一个版本中使用的模板专业化解决方案,我将提供他在那里使用的ADL解决方案:< / p>

namespace N { 
    struct adl {}; 

    template <typename A, typename T>
    T sum(A, T t){ 
        return t;
    }   

    template <typename A, typename T, typename ...U>
    auto sum(A a, T t, U... u) -> decltype(t + sum(a, u...)) {
        return t + sum(a, u...);
    }   
}

template <typename... Args>
auto sum(Args... args) -> decltype(sum(N::adl{}, args...))
{
    return sum(N::adl{}, args...);
}

这样做的原因是sum的尾随返回类型中使用的N::sum是一个从属名称,并且[temp.dep.res]具有以下查找规则:< / p>

  

在解析依赖名称时,会考虑以下来源的名称:
  (1.1) - 在模板定义时可见的声明   (1.2) - 来自名称空间的声明与函数参数的类型相关联   实例化上下文(14.6.4.1)和定义上下文。

由于lookup包含在定义点定义上下文中可见的声明,N::sum可以递归地找到它自己。

但是,我同意Yakk的说法,这种做法更令人困惑。

答案 2 :(得分:2)

引自[basic.scope.pdecl]:

  

名称的声明点在完成后立即生效   声明者(第8条)和初始化者(如果有的话)

第二个函数模板的声明在尾部返回类型decltype(t + sum(u...))之后完成。因此,在解析decltype(t + sum(u...))时,第二个模板尚未在范围内,并且编译器只能看到与调用不匹配的第一个模板。

一种可能的解决办法:

template <typename... T>
struct ReturnType;

template <typename T>
struct ReturnType<T> {
  typedef T Type;
};

template <typename T, typename... U>
struct ReturnType<T, U...> {
  typedef typename ReturnType<U...>::Type Type_;
  typedef decltype(std::declval<T>() + std::declval<Type_>()) Type;
};

template <typename T>
T sum(T t){
    return t;
}

template <typename T, typename ...U>
typename ReturnType<T, U...>::Type sum(T t, U... u) {
    return t + sum(u...);
}