繁体   English   中英

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

[英]Variadic template recursive return type deduction compilation error

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

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

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

在这里,我们将工作转发给帮助器类型:

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 ,我可以选择采用我期望它返回的类型,如果是,它会接受零长度总和(创建默认实例),但不要求类型如果我传递一个或多个参数,则为默认构造。 但我喜欢过度设计的通用代码:

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

这使我想要能够写“做这个总和,但在继续之前将每个子总和投入R ”或者某些。

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

要在C ++ 14中解决此问题,您可能必须使用具有R返回值的continuation传递样式。

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

Matrix m = sum( many_matrices... );

在Eigen工作(例如)。

当你第一次开始编写通用代码时,你必须问自己“兔子洞到底有多深?”

为了完整性,因为在这个版本的类似问题上Yakk发布了我在另一个中使用的模板专业化解决方案,我将提供他在那里使用的ADL解决方案:

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]以下查找规则:

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

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

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

引自[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...);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM