Recursive variation pattern with an empty parameter package (to avoid duplication for the base case)

I am experimenting with recursive C ++ patterns and I don't know why my pattern is not working.

Let's say I want to define a recursive function that takes a variable number of arguments (for different types).

I looked through many examples of variational patterns, and everything that I have seen so far uses a separate specialized specialization to indicate the base case.

However, I think it would be better (in some cases, at least) to use a single pattern that defines the base case, as well as recursive cases.

I think this approach is especially good if you have a lot of common logic in the function that you have to duplicate for your base example (exactly the same code in two different places).

The second template in the example below should be my solution. I would have thought that this template should function on its own. However, it is not.

Without the first template, the code does not compile:

error: no matching function for call to
      'add_elems'
        return head[i] + add_elems(i, second, tail...);
                         ^~~~~~~~~
in instantiation of function
      template specialization 'add_elems<double, std::__1::vector<double, std::__1::allocator<double> >>' requested here

...

Apparently, the template slows down when it tailconsists of only one parameter. But it should not add_elems(i, second, tail...), but still be valid for a template template<typename V, typename S, typename... T> V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)with an empty one tail?

I don't know if this depends on the compiler, but I use clang.

#include <iostream>
#include <vector>

/* This template is the exact same as the below template with an
      empty parameter pack as tail. I want my code to be working 
      without this specialisation */
template<typename V, typename S>
V add_elems(size_t i, const std::vector<V>& head, const S& second)
{
    /* Imagine some more code here */
    return head[i] + second[i];
}

template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
    /* Imagine some more code here (the same as above) */

    if (sizeof...(tail) > 0)
        return head[i] + add_elems(i, second, tail...);
    else
        return head[i] + second[i];
}

int main()
{
    std::vector<double> a({1, -3, -3});
    std::vector<double> b({2, -2, 1});
    std::vector<double> c({4, -4, -11});
    std::vector<double> d({4, 10, 0});

    std::cout << "Result: " << add_elems(0, a, b, c, d);
    std::cout << " ," << add_elems(1, a, b, c, d);
    std::cout << " ," << add_elems(2, a, b, c, d);
}
+4
source share
5 answers

, if constexpr. , add_elems

, , tail , add_elems(size_t&, const, std::vector<double>&), , second.

constexpr if, , , false, :

template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
    if constexpr (sizeof...(tail) > 0)
        return head[i] + add_elems(i, second, tail...);
    else
        return head[i] + second[i];
}

( Clang 3.9.1 -std=c++1z).

, ++ 17, :

template<typename... T>
decltype(auto) add_elems(size_t i, const T&... elems)
{
    return (elems[i] + ...);
}

2 ( Clang 3.6.0 -std=c++1z).

+4

++ 17, ++ 11, AndyG one

template <typename T0, typename ... T>
auto add_elems2 (size_t i, T0 const & elem0, T const & ... elems)
   -> decltype(elem0[i])
 {
   using unused=int[];

   auto ret = elem0[i];

   unused a { (ret += elems[i], 0)... };

   return ret;
 }
+2

, , , , add_elems(i, second, tail...) , , . , if constexpr, , ++ 1z .

@AndyG , ++ 1z , - if constexpr, " ". () .

// Only in c++1z
template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
    /* Imagine some more code here (the same as above) */

    if constexpr (sizeof...(tail) > 0)
        return head[i] + add_elems(i, second, tail...); // this is not required to be valid when the if is false
    else
        return head[i] + second[i]; // this is not required to be valid when the if is true (but it is happens to be valid anyway)
}
+1

, ++ 1z . ++ 14, .

template<class True, class False>
True pick( std::true_type, True t, False ) {
  return std::move(t);
}
template<class True, class False>
False pick( std::false_type, True, False f ) {
  return std::move(f);
}
template<bool b>
constexpr std::integral_constant<bool, b> bool_k;


template<typename V, typename S, typename... T>
V add_elems(size_t i, const std::vector<V>& head, const S& second, const T&... tail)
{
  return
    pick( bool_k<(sizeof...(tail)>0)>,
      [&](const auto&... tail)->V{
        // tail... template argument hides function argument above:
        return head[i] + add_elems(i, second, tail...);
      },
      [&]()->V{
        return head[i] + second[i];
      }
    )
    ( tail... );
};

pick lambdas.

lambdas , auto, . , auto ( , " " ), ++.

, , - . ++ 11 lambdas, " " ++ 11.

+1

Boost.Hana if constexpr ++ 14. :

template <typename...>
struct is_empty_pack : hana::integral_constant<bool, false> {};
template <>
struct is_empty_pack<> : hana::integral_constant<bool, true> {};

template <typename T, typename... Ts>
auto sum(T const& t, Ts const&... ts) {
  return hana::if_(is_empty_pack<Ts...>{},
    [](auto const& t) { return t; },
    [](auto const& t, auto const&... ts) { return t + sum(ts...); }
  )(t, ts...);
}
+1
source

Source: https://habr.com/ru/post/1674306/


All Articles