Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive variadic template with empty parameter pack (to avoid duplication for base case)

I am experimenting with C++ recursive templates and I do not know why my template is not working.

Say I want to define a recursive function that takes a variable number of arguments (for different types).

I've have looked at many examples of variadic templates, and all that I've seen so far use a separate template specialisation to specify the base case.

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

I think this approach is especially nice if you have a lot of common logic in the function, which you would have to duplicate for your base case instance (exact same code in two different places).

The second template in the example below is supposed to be my solution. I would think that this template should be functioning on it's own. However this is not the case.

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 braks when tail consists of just one parameter. But shouldn't add_elems(i, second, tail...) then still be valid for the 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 tail?

I do not know if this is compiler dependent, but I am using 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);
}
like image 502
Tivaro Avatar asked Jan 05 '23 05:01

Tivaro


1 Answers

The problem is that your if statement is not constexpr. Meaning that all code paths need to be compilable for every potential call to add_elems

This means that eventually you end up at a case where tail is just one element, and the compiler needs to evaluate add_elems(size_t&, const, std::vector<double>&), which doesn't exist because there's no second argument.

If you were able to have a constexpr if statement, then this would all work nicely because the compiler wouldn't even compile the bad branch when it evaluates to false, and therefore wouldn't look for a nonexistent function:

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

Demo (requires Clang 3.9.1 or greater and -std=c++1z option.)

For what it's worth, if you have access to C++17, you can achieve this with a unary right fold:

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

Demo 2 (requires Clang 3.6.0 or greater and -std=c++1z option.)

like image 67
AndyG Avatar answered Jan 11 '23 23:01

AndyG