I have a template function with varargs template arguments, like this
template
void ascendingPrint(
The overal approach consists in packing the arguments into an std::tuple of references, exploiting the perfect forwarding machinery of std::forward_as_tuple().
This means that, at run-time, you should incur in very small overhead and no unnecessary copy/move operations. Also, the framework does not use recursion (apart from compile-time recursion, which is unavoidable for generating indices), so no risk of run-time overhead even in case the compiler would not manage to inline the recursive function calls (which is unlikely anyway, so this is more of an academic argument).
Moreover, this solution is general, in that you can use it as a header-only library to invoke your functions with reversed arguments and with minimum effort: descending_print() should be just a minimal thin wrapper around ascending_print().
Here is how it should look like:
MAKE_REVERT_CALLABLE(ascending_print)
template
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward(args)...);
}
What follows is a presentation of the implementation.
Here is a simple way to revert a type sequence:
#include
#include
template
struct append_to_type_seq { };
template
struct append_to_type_seq>
{
using type = std::tuple;
};
template
struct revert_type_seq
{
using type = std::tuple<>;
};
template
struct revert_type_seq
{
using type = typename append_to_type_seq<
T,
typename revert_type_seq::type
>::type;
};
A small test program:
int main()
{
static_assert(
std::is_same<
revert_type_seq::type,
std::tuple
>::value,
"Error"
);
}
And a live example.
The next step consists in reverting a tuple. Given the usual indices trick machinery:
template
struct index_list { };
namespace detail
{
template
struct range_builder;
template
struct range_builder
{
typedef index_list type;
};
template
struct range_builder : public range_builder
{ };
}
template
using index_range = typename detail::range_builder::type;
Together with the functions defined above, a tuple can easily be reverted this way:
template
typename revert_type_seq::type
revert_tuple(std::tuple t, index_list)
{
using reverted_tuple = typename revert_type_seq::type;
// Forwarding machinery that handles both lvalues and rvalues...
auto rt = std::forward_as_tuple(
std::forward<
typename std::conditional<
std::is_lvalue_reference<
typename std::tuple_element::type
>::value,
typename std::tuple_element::type,
typename std::remove_reference<
typename std::tuple_element::type
>::type
>::type
>(std::get(t))...
);
return rt;
}
template
typename revert_type_seq::type
revert_tuple(std::tuple t)
{
return revert_tuple(t, index_range<0, sizeof...(Args)>());
}
Here is a simple test program:
#include
int main()
{
std::tuple t(42, 1729, 'c');
auto rt = revert_tuple(t);
std::cout << std::get<0>(rt) << " "; // Prints c
std::cout << std::get<1>(rt) << " "; // Prints 1729
std::cout << std::get<2>(rt) << " "; // Prints 42
}
Here is a live example.
The final step consists in unpacking the tuple when calling our target function. Here is another generic utility to save us a couple of lines:
template
typename revert_type_seq::type
make_revert(Args&&... args)
{
auto t = std::forward_as_tuple(std::forward(args)...);
return revert_tuple(t);
}
The above function creates a tuple whose elements are the arguments provided, but in reverse order. We are not ready to define our target:
template
void ascending_print(T&& t)
{
std::cout << std::forward(t) << " ";
}
template
void ascending_print(T&& t, Args&&... args)
{
ascending_print(std::forward(t));
ascending_print(std::forward(args)...);
}
The above function(s) prints all the arguments provided. And here is how we could write descending_print():
template
void call_ascending_print(T&& t, index_list)
{
ascending_print(std::get(std::forward(t))...);
}
template
void descending_print(Args&&... args) {
call_ascending_print(make_revert(std::forward(args)...),
index_range<0, sizeof...(Args)>());
}
A simple test case again:
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
And of course a live example.
The above solution may be non-trivial to understand, but it can be made trivial to use, and quite flexible. Given a couple of generic functions:
template
void revert_call(F&& f, index_list, Args&&... args)
{
auto rt = make_revert(std::forward(args)...);
f(std::get(rt)...);
}
template
void revert_call(F&& f, Args&&... args)
{
revert_call(f, index_range<0, sizeof...(Args)>(),
std::forward(args)...);
}
And a couple of macro definitions (I couldn't find a way to create an overload set for a function template, sorry):
#define MAKE_REVERT_CALLABLE(func) \
struct revert_caller_ ## func \
{ \
template void operator () (Args&&... args) \
{ func(std::forward(args)...); } \
};
#define REVERT_ADAPTER(func) \
revert_caller_ ## func()
It becomes really easy to adapt any function for being called with arguments in reverse order:
MAKE_REVERT_CALLABLE(ascending_print)
template
void descending_print(Args&&... args)
{
revert_call(REVERT_ADAPTER(ascending_print), std::forward(args)...);
}
int main()
{
ascending_print(42, 3.14, "Hello, World!");
std::cout << std::endl;
descending_print(42, 3.14, "Hello, World!");
}
To conclude, as usual, a live example.