Reputation: 30118
I have the following code that works, but I am confused about how it works.
template<typename ...Args>
void print(Args&&... args) {
(std::cout << ... << std::forward<Args>(args)) << '\n';
}
int main()
{
print(1,2.0,"3");
}
output:
123
My confusion:
I would expect 321 printed.
I would like to have this order:
cout << forward(args) << ...
but I can not get that to compile...
Upvotes: 2
Views: 682
Reputation: 45684
The standard go-to solution for template-magic is std::index_sequence
.
And for making arguments indexable one uses std::tuple
.
template <std::size_t... N, class T>
void print_reverse_impl(std::index_sequence<N...>, std::ostream& os, T t) {
(os << ... << std::get<std::tuple_size_v<T> - N - 1>(t));
}
template <class... T>
void print_reverse(std::ostream& os, T&&... t) {
print_reverse_impl(std::make_index_sequence<sizeof...(t)>(), os, std::forward_as_tuple(t...));
}
Still, if you have static_for()
in your tool-box (you really should), this is simpler:
template <class... T>
void print_reverse(std::ostream& os, T&&... t) {
static_for<sizeof...(t)>([&](auto n){
os << std::get<sizeof...(t) - n - 1>(std::forward_as_tuple(t...));
});
}
With C++20, one could also write it as:
void print_reverse(std::ostream& os, auto&&... t) {
[&]<auto... N>(std::index_sequence<N...>, auto all){
(os << ... std::get<sizeof...(t) - N - 1>(all));
}(std::make_index_sequence<sizeof...(t)>(), std::forward_as_tuple(t...));
}
As an aside, I cut out all the calls to std::forward
, because those rvalue-references would just be reduced down to lvalue-references by the standard-library anyway.
Upvotes: 2
Reputation: 170203
Fold expressions respect the precedence and associativity of the operator you use. But for certain operators you can do more creative left and right folds. The only variable to account for is the sequencing of the operands. C++17 introduced a happens before relation between the right and left side of assignment operators, so they act more intuitively. The right side, and all associated side effects must happen first.
So a completely self contained solution to your question can look like this:
template <typename... Args>
void print(Args&& ...args) {
int dum = 0;
(... = (std::cout << args, dum));
}
Here it is, live.
It's using a comma to print, while assigning dum
to itself in a way that forces the evaluation order we want.
Upvotes: 3
Reputation: 2324
The position of ...
specifies left or right associativity, but doesn't change the order of arguments - it lets you choose between (std::cout << x) << y
and std::cout << (x << y)
. The later likely would not compile.
If you want to print values in the reversed order you need to use some trick. Here is the example:
#include <type_traits>
#include <iostream>
template <typename T>
struct ReversePrinter
{
ReversePrinter(T val) : val(val) { }
template <typename U>
ReversePrinter<T> operator<<(const ReversePrinter<U>& other) const
{
std::cout << other.val;
return *this;
}
T val;
};
template <typename T>
std::ostream& operator<<(std::ostream& stream, const ReversePrinter<T>& val)
{
return stream << val.val;
}
template <typename... Args>
void print(Args... args)
{
std::cout << (ReversePrinter(args) << ...);
}
int main()
{
print(100, 200, 300.0); //prints 300200100
}
Upvotes: 3
Reputation: 47824
With few trickeries, (can't come anything better at the moment) you could do following:
Not sure if there's any straight forward way
// Your original function
template<typename ...Args>
void print(Args&&... args) {
(std::cout << ... << std::forward<Args>(args)) << '\n';
}
template<typename ...Args>
struct changeorder;
template<>
struct changeorder<>
{
template<typename ...OtherArgs>
static void invoke(OtherArgs const&... otherargs)
{
print(otherargs...);
}
};
template<typename T, typename ...Args>
struct changeorder<T, Args...>
{
template<typename ...OtherArgs>
static void invoke(T const& t, Args const&... args,
OtherArgs const&... otherargs)
{
// 1st parameter send first
changeorder<Args...>::invoke(args..., t, otherargs...);
}
};
template<typename A, typename ...Args>
void reverseprint(A const& a, Args const&... args)
{
changeorder<Args...>::invoke(args..., a);
}
Upvotes: 2