Variadic template recursive return type deduction compilation error

后端 未结 3 1284
一整个雨季
一整个雨季 2021-01-05 03:38

Why the code below does not compile?

template 
T sum(T t){
    return t;
}

template 
auto sum(T t, U... u         


        
3条回答
  •  不知归路
    2021-01-05 04:30

    Here we forward the work to helper types:

    namespace details {
      template
      struct sum_t {};
    
      template
      struct sum_t {
        T operator()(T t)const{ return std::forward(t); }
      };
    
      template
      struct sum_t {
        auto operator()(T t, Ts...ts)const
        -> decltype( std::declval() + sum_t{}(std::declval()...) )
        {
          return std::forward(t) + sum_t{}(std::forward(ts)...);
        }
      };
    }
    
    template
    auto sum(Ts...ts)
    -> decltype( details::sum_t{}(std::declval()...) )
    // -> std::result_of_t(Ts...)>
    // above line is C++14 and cleaner version of previous line
    {
      return details::sum_t{}(std::forward(ts)...);
    }
    

    the basic problem was that a template function cannot see itself when calculating its own return type in a -> decltype clause.

    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. 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. But I like over-engineered generic code:

    template{},
      R0,
      std::result_of_t(Ts...)>
    >>
    R sum(Ts...ts)
    {
      return details::sum_t{}(std::forward(ts)...);
    }
    

    where I modify sum_t to take the return type as the first parameter:

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

    which makes me want to be able to write "do this sum, but cast each sub-sum to R before continuing" or somesuch.

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

    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?"

提交回复
热议问题