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

Lun*_*oul 7 c++ variadic-templates c++11 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);
}
Run Code Online (Sandbox Code Playgroud)

编译错误:

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

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

Yak*_*ont 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)...);
}
Run Code Online (Sandbox Code Playgroud)

基本问题是模板函数在计算子句中自己的返回类型时无法看到自己-> 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)...);
}
Run Code Online (Sandbox Code Playgroud)

我修改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)...);
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

这让我希望能够写“计算总和,但R在继续之前将每个子总和转换为”或类似的内容。

In C++1z, you'll want to use a fold-expression instead. Being able to set R is still useful, as if you are adding up an expression template, it may only be valid as an expression template until the end of the current scope.

To fix this problem in C++14, you may have to use continuation passing style with the R return value.

We could then fold return type deduction into the game to allow

Matrix m = sum( many_matrices... );
Run Code Online (Sandbox Code Playgroud)

to work in Eigen (for example).

When you first start to write generic code, you have to ask yourself "how deep down the rabbit hole do we want to go?"