NoSenseEtAl
NoSenseEtAl

Reputation: 30118

Fold expressions and cout

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

Answers (4)

Deduplicator
Deduplicator

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

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

Dmitry Gordon
Dmitry Gordon

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

P0W
P0W

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

Demo Here

Upvotes: 2

Related Questions