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);
}
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.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With