I'm very new to meta-programming and I was trying to play around with variadic templates. I've searched a lot on StackOverflow and came across multiple solutions for my problem but I couldn't seem to make them work. The code I have is the following:
template <typename TA, typename... TB>
class A
{
public:
inline long long f()
{
// 6 is just a placeholder, the value
// comes from a dummy object which is a class member
return g<TB...>(6);
}
[...]
private:
template <typename Head, typename Head2, typename ...Tail>
inline long long g(long long x)
{
return (&Head::instance())->external_function(g<Head2, Tail...>(x));
}
template<typename Head>
inline long long g(long long x) {
return (&Head::instance())->external_function(x);
}
inline long long g(long long x) {
return x;
}
[...]
Where TB can be 0 or more elements.
So what I was trying to achieve is to call a method external_function
in cascade which I know every element inside TB has defined.
It works if TB has one element, but if TB is empty, what I get is that I have no function matching g<>.
Then I tried to redefine the three overloaded functions as follows
template <typename Head, typename ...Tail>
inline long long g(long long x)
{
return (&Head::instance())->external_function(g<Tail...>(x));
}
template<typename Head>
inline long long g(long long x) {
return (&Head::instance())->external_function(x);
}
template<typename = void>
inline long long g(long long x) {
return x;
}
But still no luck.
So what I tried again was to use two specialised functions, one that accepts one or more arguments and the other that is called only when there are zero arguments
template <typename Head, typename ...Tail>
inline long long g(long long x)
{
return (&Head::instance())-> external_function(g<Tail...>(x));
}
template<typename... Tail>
inline long long g(long long x) {
return x;
}
But again, what I get is that this last solution only works if TB is empty. If it contains at least one element I get an error about an ambiguous call.
I'm getting lost with variadic functions and their base cases also because their usage has changed a lot during the years with C++11, C++17 and C++20. I've seen that fold expressions can solve the problem and make the code more readable, but I had no luck trying to use them for cascade calls.
Really hope someone can help me shed light on this.
Edit: For project reasons, I'm stuck with C++14 and below.
With C++14, you can achieve what you need by using partial class specialization:
template <typename TA, typename... TB>
class A
{
public:
inline long long f()
{
// 6 is just a placeholder, the value
// comes from a dummy object which is a class member
return g_impl<TB...>::call(6LL);
}
private:
// base specialization, fallback for 0 elements
template<typename... Ts>
struct g_impl
{
static inline long long call(long long x)
{
std::cout << "empty typelist case\n";
return x;
}
};
// 1 or more elements
template< typename Head, typename... Tail>
struct g_impl<Head, Tail...>
{
static inline long long call(long long x)
{
std::cout << (sizeof...(Tail) + 1) << " types case\n";
return (&Head::instance())->external_function(g_impl<Tail...>::call(x));
}
};
};
Let's test with the following identical classes:
struct Test1
{
long long external_function(long long x){std::cout << "Test1::external_function\n"; return 42;}
static Test1& instance()
{
static Test1 self;
return self;
}
};
struct Test2
{
long long external_function(long long x){std::cout << "Test2::external_function\n"; return 42;}
static Test2& instance()
{
static Test2 self;
return self;
}
};
struct Test3
{
long long external_function(long long x){std::cout << "Test3::external_function\n"; return 42;}
static Test3& instance()
{
static Test3 self;
return self;
}
}
And call it from main
like so:
int main()
{
std::cout << "---\n";
std::cout << "Testing A<Test1>\n";
A<Test1> a;
a.f();
std::cout << "---\n";
std::cout << "Testing A<Test1, Test2>\n";
A<Test1, Test2> b{};
b.f();
std::cout << "---\n";
std::cout << "Testing A<Test1, Test2, Test3>\n";
A<Test1, Test2, Test3> c;
c.f();
std::cout << "---\n";
}
Output:
---
Testing A<Test1>
empty typelist case
---
Testing A<Test1, Test2>
1 types case
empty typelist case
Test2::external_function
---
Testing A<Test1, Test2, Test3>
2 types case
1 types case
empty typelist case
Test3::external_function
Test2::external_function
---
In C++20 we can squeeze this all into a single recursive template function:
template<typename... Ts>
inline long long g(long long x)
{
// empty typelist
if constexpr(sizeof...(Ts) == 0)
{
std::cout << "Empty typelist case\n";
return x;
}
else
{
// 1 or more in typelist
std::cout << sizeof...(Ts) << " in typelist\n";
auto callable = [=]<class Head, class... Tail>
{
return Head::instance().external_function(g<Tail...>(x));
};
return callable.template operator()<Ts...>();
}
}
EDIT: Per OP's request, they'd also like to reverse the parameter pack before the calls, which requires some boilerplate to create the notion of a typelist (empty class) and perform operations on it to reverse it, and then later extract the types:
template<class... Ts>
struct typelist{};
template<class... Ts, class... Us>
auto concat(typelist<Ts...>, typelist<Us...>) -> typelist<Ts..., Us...>;
auto reverse(typelist<>) -> typelist<>;
template<class Head, class... Tail>
auto reverse(typelist<Head, Tail...>)
-> decltype(
concat(
reverse(std::declval<typelist<Tail...>>()),
std::declval<typelist<Head>>()
) // concat
); // decltype
template<class... Ts>
using reversed_typelist = decltype(reverse(std::declval<typelist<Ts...>>()));
Then we add another level of indirection on calling f()
:
public:
inline long long f()
{
// 6 is just a placeholder, the value
// comes from a dummy object which is a class member
return f_impl(reversed_typelist<TB...>{}, 6LL);
}
private:
template<class... Ts>
inline long long f_impl(typelist<Ts...>, long long x)
{
return g_impl<Ts...>::call(x);
}
Now that we've reversed the parameter pack, the output demonstrates the calls in reverse order as before:
---
Testing A<Test1>
empty typelist case
---
Testing A<Test1, Test2>
1 types case
empty typelist case
Test2::external_function
---
Testing A<Test1, Test2, Test3>
2 types case
1 types case
empty typelist case
Test2::external_function
Test3::external_function
---