Свернуть выражения и cout

У меня есть следующий код, который работает, но я не понимаю, как он работает.

template<typename ...Args>
void print(Args&&... args) {
    (std::cout << ... << std::forward<Args>(args)) << '\n';
}
int main()
{
    print(1,2.0,"3");
}

выход:

123

Мое замешательство:

Я бы ожидал 321 печатного.

Я хотел бы иметь такой порядок:

cout << forward(args) << ... 

но я не могу заставить это скомпилировать...


person NoSenseEtAl    schedule 19.04.2019    source источник
comment
Вы сделали print(1,2.0,"3"); и ожидали 321?   -  person PiotrNycz    schedule 19.04.2019
comment
@Holt Связанный ответ не является дубликатом. (неполный для поста ОП). Это просто объясняет, почему возникла ошибка компилятора.   -  person P0W    schedule 19.04.2019
comment
@ Холт, я согласен, что вопрос не дублируется. По крайней мере, это не дает ответа, как печатать значения в обратном порядке.   -  person Dmitry Gordon    schedule 19.04.2019
comment
@PiotrNycz похоже, что сначала выбирается хвост (...), а затем голова (аргументы)   -  person NoSenseEtAl    schedule 19.04.2019
comment
en.cppreference.com/w/cpp/language/fold   -  person Jarod42    schedule 19.04.2019
comment
@NoSenseEtAl, позиция ... указывает левую или правую ассоциативность, но не меняет порядок аргументов — она позволяет выбирать между (std::cout ‹‹ x) ‹‹ y и std::cout ‹‹ ( х ‹‹ у). Позднее, вероятно, не будет компилироваться   -  person Dmitry Gordon    schedule 19.04.2019
comment
@DmitryGordon а, это ответ, спасибо, я бы хотел, чтобы это не закрывалось. :)   -  person NoSenseEtAl    schedule 19.04.2019


Ответы (4)


Позиция ... определяет левостороннюю или правую ассоциативность, но не меняет порядок аргументов — она позволяет выбирать между (std::cout << x) << y и std::cout << (x << y). Позднее, вероятно, не будет компилироваться.

Если вы хотите напечатать значения в обратном порядке, вам нужно использовать некоторый трюк. Вот пример:

#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
}
person Dmitry Gordon    schedule 19.04.2019
comment
Это много копирования. - person Deduplicator; 19.04.2019

Выражения свертки учитывают приоритет и ассоциативность используемого вами оператора. Но для некоторых операторов вы можете сделать более творческие левые и правые сгибы. Единственная переменная, которую необходимо учитывать, — это последовательность операндов. В C++17 введено отношение «происходит до» между правой и левой частями операторов присваивания, поэтому они действуют более интуитивно. Сначала должна произойти правая сторона и все связанные с ней побочные эффекты.

Таким образом, полностью автономное решение вашего вопроса может выглядеть так:

template <typename... Args>
void print(Args&& ...args) {
   int dum = 0;
   (... = (std::cout << args, dum));
}

Вот оно, вживую.

Он использует запятую для печати, присваивая dum самому себе таким образом, что задает порядок оценки, который мы хотим.

person StoryTeller - Unslander Monica    schedule 19.04.2019

С несколькими хитростями (на данный момент ничего лучше не может быть) вы можете сделать следующее:

Не уверен, что есть прямой путь

// 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

person P0W    schedule 19.04.2019

Стандартное решение для магии шаблонов — std::index_sequence.
А для индексации аргументов используется 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...));
}

Тем не менее, если у вас есть static_for() в вашем наборе инструментов (вам действительно нужно), это проще:

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

С С++ 20 это можно было бы также написать так:

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

Кроме того, я вырезал все вызовы std::forward, потому что эти rvalue-ссылки в любом случае будут сокращены до lvalue-ссылок стандартной библиотекой.

person Deduplicator    schedule 19.04.2019