简体   繁体   English

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

[英]Variadic template recursive return type deduction compilation error

Why the code below does not compile? 为什么以下代码无法编译?

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

Compilation error: 编译错误:

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

What would be a good workaround to implement this feature? 实现此功能的好方法是什么?

Here we forward the work to helper types: 在这里,我们将工作转发给帮助器类型:

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

the basic problem was that a template function cannot see itself when calculating its own return type in a -> decltype clause. 基本问题是模板函数在-> decltype子句中计算自己的返回类型时看不到自己。

There are a few work arounds. 有几种解决方法。 The above should work, because a template class can see other specializations of its partial specialization in its own body. 上面应该可以工作,因为模板类可以在自己的主体中看到其部分特化的其他特化。 Another approach would be to use Koenig lookup (ADL) to defer the searching for its recursive call until the point of instantiation, where it can find itself. 另一种方法是使用Koenig查找(ADL)来推迟搜索其递归调用,直到它可以找到它自己的实例化点。 I find that second approach more confusing. 我发现第二种方法更令人困惑。

If I was to write my own sum for production, I'd have it optionally take the type I expect it to return, and if it did it would accept a zero length sum (creating a default instance), but not requires that the type be default constructable if I pass 1 or more arguments. 如果我要为生产编写自己的sum ,我可以选择采用我期望它返回的类型,如果是,它会接受零长度总和(创建默认实例),但不要求类型如果我传递一个或多个参数,则为默认构造。 But I like over-engineered generic code: 但我喜欢过度设计的通用代码:

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

where I modify sum_t to take the return type as the first parameter: 我修改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)...);
    }
  };
}

which makes me want to be able to write "do this sum, but cast each sub-sum to R before continuing" or somesuch. 这使我想要能够写“做这个总和,但在继续之前将每个子总和投入R ”或者某些。

In C++1z, you'll want to use a fold-expression instead. 在C ++ 1z中,您将需要使用fold-expression。 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. 能够设置R仍然有用,就像您要添加表达式模板一样,它可能仅在当前范围结束之前作为表达式模板有效。

To fix this problem in C++14, you may have to use continuation passing style with the R return value. 要在C ++ 14中解决此问题,您可能必须使用具有R返回值的continuation传递样式。

We could then fold return type deduction into the game to allow 然后我们可以将返回类型扣除折叠到游戏中以允许

Matrix m = sum( many_matrices... );

to work in Eigen (for example). 在Eigen工作(例如)。

When you first start to write generic code, you have to ask yourself "how deep down the rabbit hole do we want to go?" 当你第一次开始编写通用代码时,你必须问自己“兔子洞到底有多深?”

In the interests of completeness, since on this version of a similar question , Yakk posted the template specialization solution that I used in the other one, I will provide the ADL solution that he used there: 为了完整性,因为在这个版本的类似问题上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...);
}

The reason this works is that the sum used in the trailing-return-type of N::sum is a dependent name, and has the following lookup rules from [temp.dep.res]: 这个工作的原因是,该sum在尾返回型的使用N::sum是一个从属名称,并且具有从[temp.dep.res]以下查找规则:

In resolving dependent names, names from the following sources are considered: 在解析依赖名称时,会考虑以下来源的名称:
(1.1) — Declarations that are visible at the point of definition of the template. (1.1) - 在模板定义时可见的声明。
(1.2) — Declarations from namespaces associated with the types of the function arguments both from the instantiation context (14.6.4.1) and from the definition context. (1.2) - 来自实例化上下文(14.6.4.1)和定义上下文中与函数参数类型相关联的名称空间的声明。

Since lookup includes declarations that are visible at point of definition and the definition context , N::sum can find itself recursively. 由于查找包括在定义点定义上下文中可见的声明,因此N::sum可以递归地找到它自己。

However, I agree with Yakk that this approach is more confusing. 但是,我同意Yakk的说法,这种做法更令人困惑。

Quoted from [basic.scope.pdecl]: 引自[basic.scope.pdecl]:

The point of declaration for a name is immediately after its complete declarator (Clause 8) and before its initializer (if any) 名称的声明点紧接在其完整的声明者(第8条)之后和其初始化者(如果有的话)之前

The declaration of the second function template is complete after the trailing return type decltype(t + sum(u...)) . 在尾随返回类型decltype(t + sum(u...))之后完成第二个函数模板的声明。 So, when parsing decltype(t + sum(u...)) , the second template is not in scope yet, and the compiler can only see the first template which does not match the call. 因此,在解析decltype(t + sum(u...)) ,第二个模板尚未在范围内,并且编译器只能看到与调用不匹配的第一个模板。

One possible fix: 一个可能的解决方

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