Подтвердить что ты не робот

С++ 17 Шаблоны шаблонов Variadic

Я не понимаю, почему это не работает. Может ли кто-то, кто разбирается в шаблонах и сворачивании вариантов, объясняет, что происходит и дает решение, которое действительно работает?

#include <iostream>
#include <string>

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << sep << args) << end;
}

int main()
{
    print(1, 2, 3);
}

Он должен распечатать каждый из аргументов с пробелом между ними и новой строкой в ​​конце. Он работает, если вы удалите sep <<, но тогда нет места между каждым аргументом при его печати. ​​

4b9b3361

Ответ 1

Грамматика для двоичных fold-expressions должна быть одной из следующих:

(pack op ... op init)
(init op ... op pack)

У вас есть (std::cout << ... << sep << args), который не соответствует любой форме. Вам нужно что-то вроде (cout << ... << pack), поэтому удаляется sep.

Вместо этого вы можете либо сбрасывать запятую:

((std::cout << sep << args), ...);

или используйте рекурсию:

template <class A, class... Args>
void print(A arg, Args... args) {
    std::cout << arg;
    if constexpr (sizeof...(Args) > 0) {
        std::cout << sep;
        print(args...);
    }
}

Ответ 2

Это будет работать, но оно напечатает конечное пространство:

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";

    ((std::cout << args << sep), ...) << end;
}

пример live wandbox


В этом случае выполняется свертка над оператором запятой, что приводит к расширению, подобному:

// (pseudocode)
(std::cout << args<0> << sep), 
(std::cout << args<1> << sep),
(std::cout << args<2> << sep), 
...,
(std::cout << args<N> << sep), 

Ответ 3

Что вы действительно хотите сделать:

std::string sep = " ";
std::string end = "\n";
(std::cout << ... << (sep << args)) << end;

потому что вы хотите, чтобы (sep << args) был сложен с помощью std::cout. Это не работает, потому что sep << args не знает, что он передается на std::cout или передается вообще; << работает только потоком, если левая сторона является потоком.

Короче говоря, проблема в том, что sep << args не понимает, что это потоковая передача.

Ваша другая проблема недостаточна лямбда.

Мы можем исправить это.

template<class F>
struct ostreamer_t {
    F f;
    friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self ) {
        self.f(os);
        return os;
    }
    template<class T>
    friend auto operator<<(ostreamer_t self, T&& t) {
        auto f = [g = std::move(self.f), &t](auto&& os)mutable {
            std::move(g)(os);
            os << t;
        };
        return ostreamer_t<decltype(f)>{std::move(f)};
    }
};

struct do_nothing_t {
    template<class...Args>
    void operator()(Args&&...)const {}
};

const ostreamer_t<do_nothing_t> ostreamer{{}};

template <typename... Args>
void print(Args... args)
{
    std::string sep = " ";
    std::string end = "\n";
    (std::cout << ... << (ostreamer << sep << args)) << end;
}

живой пример. (Я также использовал литерал для sep, чтобы я работал с rvalues).

ostreamer фиксирует ссылки на вещи << 'd, а затем выгружает их, когда в свою очередь это << на ostream.

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

Ответ 4

Как утверждают другие, вы пытаетесь использовать неправильный формат выражения. Вы можете использовать лямбда-помощник для своей цели очень простым способом:

template <typename... Args>
void print(Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    (std::cout << ... << streamSep(args)) << end;
}

Это будет следовать за поведением, ожидаемым в написанном вами коде. Однако, если вы хотите избежать sep перед первым аргументом, вы можете использовать следующее:

template <typename Arg, typename... Args>
void print(Arg&& arg, Args&&... args)
{
    std::string sep = " ";
    std::string end = "\n";
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) {
        std::cout << sep;
        return arg;
    };
    std::cout << arg;
    (std::cout << ... << streamSep(args)) << end;
}

Ответ 5

Вы можете попробовать что-то вроде этого

template <typename... Args>
void print(Args... args)
{
  bool first = true;
  auto lambda = [&](auto param)
  {
    if( !first) std::cout << ',';
    first= false;
    return param;
  };

  ((std::cout << lambda(args)), ...);
}

Лямбда гарантирует, что разделитель вставлен только между двумя элементами.

С другой стороны, если вы не хотите использовать lambdas, вы можете перегрузить шаблон:

template<typename T>
void print(T item)
{
  std::cout << item;
}

template<typename T, typename... Args>
void print(T item, Args... args)
{
  print(item);
  std::cout << ',';
  print(args...);
}

Ответ 6

Если вы не хотите, чтобы ведущий/конечный sep:

template <typename First, typename... Rest>
void print(First first, Rest... rest)
{
    std::string sep = " ";
    std::string end = "\n";

    std::cout << first;
    ((std::cout << sep << rest), ...);
    std::cout << end;
}

Вам нужно сделать std::cout << end; отдельную инструкцию для обработки случая с одним параметром.

Ответ 7

Другой подход заключается в следующем:

#include <iostream>

template<class U, class... T>
    void printSpaced(const U& u, const T&... args)
{   
     using std::cout;
     using std::endl;         

     ((cout << u) << ... << (cout << ' ', args)) << endl;   
}

Таким образом, вы не получите ведущее/конечное место
Использование:

printSpaced(1, 2, "Hello", 4.5f); //Output 1 2 Hello 4.5 and no trailing space