Как распечатать содержимое вектора?

Как мне распечатать содержимое std::vector на экране?


Также было бы неплохо решение, реализующее следующие operator<<:

template<container C, class T, String delim = ", ", String open = "[", String close = "]">
std::ostream & operator<<(std::ostream & o, const C<T> & x)
{
  // ... What can I write here?
}

Вот что у меня есть без отдельной функции:

#include <iostream>
#include <fstream>
#include <string>
#include <cmath>
#include <vector>
#include <sstream>
#include <cstdio>
using namespace std;

int main()
{
    ifstream file("maze.txt");
    if (file) {
        vector<char> vec(istreambuf_iterator<char>(file), (istreambuf_iterator<char>()));
        vector<char> path;
        int x = 17;
        char entrance = vec.at(16);
        char firstsquare = vec.at(x);
        if (entrance == 'S') { 
            path.push_back(entrance); 
        }
        for (x = 17; isalpha(firstsquare); x++) {
            path.push_back(firstsquare);
        }
        for (int i = 0; i < path.size(); i++) {
            cout << path[i] << " ";
        }
        cout << endl;
        return 0;
    }
}

person forthewinwin    schedule 25.05.2012    source источник
comment
для информации, единственный способ, которым я нашел это лаконично, был взлом - ›добавление перегрузок operator<< в пространство имен std (так, чтобы они были подхвачены ADL) и перенаправление вызовов в общий метод диапазона печати ... Я Мне очень интересны результаты этого обсуждения, спасибо за вопрос :)   -  person Matthieu M.    schedule 31.01.2011
comment
если у вас есть разнородные типы, где вы смешиваете контейнеры stl и кортежи. используйте boost.fusion io вместе с красивой печатью. cout << vector<tuple<int,array<int,3>>>(...) << endl;   -  person kirill_igum    schedule 07.08.2014


Ответы (28)


Вы можете использовать итератор:

std::vector<char> path;
// ...
for (std::vector<char>::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

Если вы хотите изменить содержимое вектора в цикле for, используйте iterator, а не const_iterator.

Но об этом можно сказать гораздо больше. Если вам просто нужен ответ, который вы можете использовать, то можете остановиться здесь; в противном случае читайте дальше.

авто (C ++ 11) / typedef / псевдоним типа (C ++ 11)

Это не другое решение, а дополнение к вышеуказанному решению iterator. Если вы используете стандарт C ++ 11 (или более поздний), вы можете использовать ключевое слово auto для повышения удобочитаемости:

for (auto i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

Но тип i будет неконстантным (т.е. компилятор будет использовать std::vector<char>::iterator как тип i).

В этом случае вы можете просто использовать typedef, который также приносит с собой свои преимущества (которые я не буду здесь подробно излагать):

typedef std::vector<char> Path; // 'Path' now a synonym for the vector
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

C ++ 11 также представил псевдоним типа, который выполняет ту же работу, что и typedef, и вы можете найти его более читаемым, чем использование typedef:

using Path = std::vector<char>; // C++11 onwards only
Path path;
// ...
for (Path::const_iterator i = path.begin(); i != path.end(); ++i)
    std::cout << *i << ' ';

прилавок

Конечно, вы можете использовать целочисленный тип для записи вашей позиции в цикле for:

for(int i=0; i<path.size(); ++i)
  std::cout << path[i] << ' ';

Если вы собираетесь это сделать, лучше использовать типы членов контейнера, если они доступны и уместны. std::vector имеет тип элемента size_type для этого задания: это тип, возвращаемый методом size.

// Path typedef'd to std::vector<char>
for( Path::size_type i=0; i<path.size(); ++i)
  std::cout << path[i] << ' ';

Почему бы просто не использовать это вместо iterator решения? В простых случаях вы тоже можете это сделать, но суть в том, что iterator - это объект, предназначенный для выполнения этой работы для более сложных объектов, где это решение не будет идеальным.

цикл for на основе диапазона (C ++ 11)

См. решение Джеффри. В C ++ 11 (и более поздних версиях) вы можете использовать новый цикл for на основе диапазона, который выглядит следующим образом:

for (auto i: path)
  std::cout << i << ' ';

Поскольку path является вектором элементов (явно std::vector<char>), объект i имеет тип элемента вектора (т. Е. Явно имеет тип char). Объект i имеет значение, которое является копией фактического элемента в объекте path. Таким образом, все изменения i в цикле не сохраняются в самом path. Кроме того, если вы хотите обеспечить соблюдение того факта, что вы не хотите иметь возможность изменять скопированное значение i в цикле, вы можете принудительно установить тип i на const char следующим образом:

for (const auto i: path)
  std::cout << i << ' ';

Если вы хотите изменить элементы в path, вы можете использовать ссылку:

for (auto& i: path)
  std::cout << i << ' ';

и даже если вы не хотите изменять path, если копирование объектов стоит дорого, вы должны использовать константную ссылку вместо копирования по значению:

for (const auto& i: path)
  std::cout << i << ' ';

std :: copy (C ++ 11)

См. ответ Джошуа. Вы можете использовать алгоритм STL std::copy для копирования содержимого вектора в выходной поток. Это элегантное решение, если оно вам удобно. Следует поощрять более глубокое знакомство с алгоритмами STL, поскольку они предоставляют множество функций, которые новички могут изобретать заново. Прочтите сообщение Джошуа для получения дополнительной информации.

перегрузить std :: ostream :: оператор ‹

См. ответ Криса, это скорее дополнение к другим ответам, поскольку вам все равно нужно будет реализовать одно из приведенных выше решений в перегрузка. В своем примере он использовал счетчик в for цикле. Например, вот как вы можете быстро использовать решение Джошуа:

#include <iterator> // needed for std::ostram_iterator

template <typename T>
std::ostream& operator<< (std::ostream& out, const std::vector<T>& v) {
  if ( !v.empty() ) {
    out << '[';
    std::copy (v.begin(), v.end(), std::ostream_iterator<T>(out, ", "));
    out << "\b\b]";
  }
  return out;
}

Использование любого из других решений должно быть столь же простым.

заключение

Любое из представленных здесь решений будет работать. Это зависит от вас и кода, который лучше всего. Что-нибудь более подробное, вероятно, лучше оставить для другого вопроса, где можно будет должным образом оценить все «за» / «против»; но, как всегда, предпочтения пользователя всегда будут иметь значение: ни одно из представленных решений не является неправильным, но некоторые из них будут выглядеть лучше для каждого отдельного программиста.

добавление

Это расширенное решение, которое я опубликовал ранее. Поскольку этот пост продолжал привлекать внимание, я решил расширить его и сослаться на другие отличные решения, которые были опубликованы здесь. В моем исходном сообщении было замечание, в котором упоминалось, что если вы намереваетесь изменить свой вектор внутри цикла for, то std::vector предоставляет два метода доступа к элементам: std::vector::operator[], который не проверяет границы, и std::vector::at который выполняет проверку границ. Другими словами, at выдаст, если вы попытаетесь получить доступ к элементу за пределами вектора, а operator[] - нет. Изначально я добавил этот комментарий только для того, чтобы упомянуть кое-что, о чем было бы полезно знать, если кто-то еще этого не сделал. И сейчас разницы не вижу. Отсюда это добавление.

person Zorawar    schedule 25.05.2012
comment
Если вы выполняете цикл с 0 по vector::size() и вектор не изменяется в цикле, нет необходимости использовать at() и нести дополнительные накладные расходы на проверку границ. Тем не менее, я бы выбрал итератор, как вы предлагаете. - person Ed S.; 05.07.2012
comment
@Ed: да, нет смысла использовать at, если ничто в цикле не изменяет вектор, но я подумал, что упомянул бы об этом на всякий случай, если вектор будет изменен в цикле (не рекомендуется, поскольку это может be) и потому, что о нем никогда не упоминают, и было бы полезно, по крайней мере, узнать о нем. - person Zorawar; 25.07.2012
comment
Цикл for на основе диапазона может быть переписан для использования ссылок, что может быть важно в случае больших подобъектов, следующим образом: for (auto const &i: path) std::cout << i << ' '; - person underscore_d; 15.07.2015
comment
@underscore_d: спасибо. Я очистил этот раздел и надеюсь, что теперь он более полон и немного понятнее. - person Zorawar; 18.07.2015
comment
оператор перегрузки ‹< - не лучшее решение; по крайней мере, один операнд перегруженного оператора должен быть классом, определенным вашей программой, из-за поиска, зависящего от аргументов - person M.M; 02.02.2017

Намного проще сделать это с помощью стандартного алгоритма копирования:

#include <iostream>
#include <algorithm> // for copy
#include <iterator> // for ostream_iterator
#include <vector>

int main() {
    /* Set up vector to hold chars a-z */
    std::vector<char> path;
    for (int ch = 'a'; ch <= 'z'; ++ch)
        path.push_back(ch);

    /* Print path vector to console */
    std::copy(path.begin(), path.end(), std::ostream_iterator<char>(std::cout, " "));

    return 0;
}

Ostream_iterator - это то, что называется адаптером итератора. Он привязан к типу для печати в потоке (в данном случае char). cout (он же вывод на консоль) - это поток, в который мы хотим писать, а символ пробела (" ") - это то, что мы хотим напечатать между каждым элементом, хранящимся в векторе.

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

person Joshua Kravitz    schedule 04.07.2012
comment
что, если бы мой вектор был типа vector<pair<int, struct node>>. Как мне распечатать этот вектор с помощью описанного выше метода? - person mtk; 23.10.2015
comment
Строка-разделитель записывается после каждого элемента, а не между, т.е. также после последнего. Это может потребовать работы с особыми случаями, если вы хотите только между ними, то есть в качестве разделителя. - person Quigi; 28.12.2015
comment
@mtk вы можете объявить operator<< функцию для вашей конкретной пары ‹›. - person ShoeLace; 30.06.2017
comment
Добавлен ответ, показывающий аналогичный подход, но с учетом комментария @Quigi: s выше относительно дополнительного конечного разделителя. - person dfrib; 30.07.2017
comment
@ShoeLace А другого выхода нет? - person thegreatcoder; 24.02.2019
comment
std :: ostreambuf_iterator специализирован для символов, вероятно, работает лучше. - person gerardw; 21.05.2019
comment
Он отлично работает с вектором типа char, но как насчет вашего собственного типа данных. F.e. Я сделал тип данных Card: он имеет значение, которое является целым числом, и набор, который является строкой. Этот подход не работает с моим примером. - person šm98; 30.12.2019

Это решение было вдохновлено решением Марсело с некоторыми изменениями:

#include <iostream>
#include <iterator>
#include <type_traits>
#include <vector>
#include <algorithm>

// This works similar to ostream_iterator, but doesn't print a delimiter after the final item
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar> >
class pretty_ostream_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void>
{
public:
    typedef TChar char_type;
    typedef TCharTraits traits_type;
    typedef std::basic_ostream<TChar, TCharTraits> ostream_type;

    pretty_ostream_iterator(ostream_type &stream, const char_type *delim = NULL)
        : _stream(&stream), _delim(delim), _insertDelim(false)
    {
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator=(const T &value)
    {
        if( _delim != NULL )
        {
            // Don't insert a delimiter if this is the first time the function is called
            if( _insertDelim )
                (*_stream) << _delim;
            else
                _insertDelim = true;
        }
        (*_stream) << value;
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator*()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++()
    {
        return *this;
    }

    pretty_ostream_iterator<T, TChar, TCharTraits>& operator++(int)
    {
        return *this;
    }
private:
    ostream_type *_stream;
    const char_type *_delim;
    bool _insertDelim;
};

#if _MSC_VER >= 1400

// Declare pretty_ostream_iterator as checked
template<typename T, typename TChar, typename TCharTraits>
struct std::_Is_checked_helper<pretty_ostream_iterator<T, TChar, TCharTraits> > : public std::tr1::true_type
{
};

#endif // _MSC_VER >= 1400

namespace std
{
    // Pre-declarations of container types so we don't actually have to include the relevant headers if not needed, speeding up compilation time.
    // These aren't necessary if you do actually include the headers.
    template<typename T, typename TAllocator> class vector;
    template<typename T, typename TAllocator> class list;
    template<typename T, typename TTraits, typename TAllocator> class set;
    template<typename TKey, typename TValue, typename TTraits, typename TAllocator> class map;
}

// Basic is_container template; specialize to derive from std::true_type for all desired container types
template<typename T> struct is_container : public std::false_type { };

// Mark vector as a container
template<typename T, typename TAllocator> struct is_container<std::vector<T, TAllocator> > : public std::true_type { };

// Mark list as a container
template<typename T, typename TAllocator> struct is_container<std::list<T, TAllocator> > : public std::true_type { };

// Mark set as a container
template<typename T, typename TTraits, typename TAllocator> struct is_container<std::set<T, TTraits, TAllocator> > : public std::true_type { };

// Mark map as a container
template<typename TKey, typename TValue, typename TTraits, typename TAllocator> struct is_container<std::map<TKey, TValue, TTraits, TAllocator> > : public std::true_type { };

// Holds the delimiter values for a specific character type
template<typename TChar>
struct delimiters_values
{
    typedef TChar char_type;
    const TChar *prefix;
    const TChar *delimiter;
    const TChar *postfix;
};

// Defines the delimiter values for a specific container and character type
template<typename T, typename TChar>
struct delimiters
{
    static const delimiters_values<TChar> values; 
};

// Default delimiters
template<typename T> struct delimiters<T, char> { static const delimiters_values<char> values; };
template<typename T> const delimiters_values<char> delimiters<T, char>::values = { "{ ", ", ", " }" };
template<typename T> struct delimiters<T, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T> const delimiters_values<wchar_t> delimiters<T, wchar_t>::values = { L"{ ", L", ", L" }" };

// Delimiters for set
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, char> { static const delimiters_values<char> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<char> delimiters<std::set<T, TTraits, TAllocator>, char>::values = { "[ ", ", ", " ]" };
template<typename T, typename TTraits, typename TAllocator> struct delimiters<std::set<T, TTraits, TAllocator>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T, typename TTraits, typename TAllocator> const delimiters_values<wchar_t> delimiters<std::set<T, TTraits, TAllocator>, wchar_t>::values = { L"[ ", L", ", L" ]" };

// Delimiters for pair
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, char> { static const delimiters_values<char> values; };
template<typename T1, typename T2> const delimiters_values<char> delimiters<std::pair<T1, T2>, char>::values = { "(", ", ", ")" };
template<typename T1, typename T2> struct delimiters<std::pair<T1, T2>, wchar_t> { static const delimiters_values<wchar_t> values; };
template<typename T1, typename T2> const delimiters_values<wchar_t> delimiters<std::pair<T1, T2>, wchar_t>::values = { L"(", L", ", L")" };

// Functor to print containers. You can use this directly if you want to specificy a non-default delimiters type.
template<typename T, typename TChar = char, typename TCharTraits = std::char_traits<TChar>, typename TDelimiters = delimiters<T, TChar> >
struct print_container_helper
{
    typedef TChar char_type;
    typedef TDelimiters delimiters_type;
    typedef std::basic_ostream<TChar, TCharTraits>& ostream_type;

    print_container_helper(const T &container)
        : _container(&container)
    {
    }

    void operator()(ostream_type &stream) const
    {
        if( delimiters_type::values.prefix != NULL )
            stream << delimiters_type::values.prefix;
        std::copy(_container->begin(), _container->end(), pretty_ostream_iterator<typename T::value_type, TChar, TCharTraits>(stream, delimiters_type::values.delimiter));
        if( delimiters_type::values.postfix != NULL )
            stream << delimiters_type::values.postfix;
    }
private:
    const T *_container;
};

// Prints a print_container_helper to the specified stream.
template<typename T, typename TChar, typename TCharTraits, typename TDelimiters>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const print_container_helper<T, TChar, TDelimiters> &helper)
{
    helper(stream);
    return stream;
}

// Prints a container to the stream using default delimiters
template<typename T, typename TChar, typename TCharTraits>
typename std::enable_if<is_container<T>::value, std::basic_ostream<TChar, TCharTraits>&>::type
    operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const T &container)
{
    stream << print_container_helper<T, TChar, TCharTraits>(container);
    return stream;
}

// Prints a pair to the stream using delimiters from delimiters<std::pair<T1, T2>>.
template<typename T1, typename T2, typename TChar, typename TCharTraits>
std::basic_ostream<TChar, TCharTraits>& operator<<(std::basic_ostream<TChar, TCharTraits> &stream, const std::pair<T1, T2> &value)
{
    if( delimiters<std::pair<T1, T2>, TChar>::values.prefix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.prefix;

    stream << value.first;

    if( delimiters<std::pair<T1, T2>, TChar>::values.delimiter != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.delimiter;

    stream << value.second;

    if( delimiters<std::pair<T1, T2>, TChar>::values.postfix != NULL )
        stream << delimiters<std::pair<T1, T2>, TChar>::values.postfix;
    return stream;    
}

// Used by the sample below to generate some values
struct fibonacci
{
    fibonacci() : f1(0), f2(1) { }
    int operator()()
    {
        int r = f1 + f2;
        f1 = f2;
        f2 = r;
        return f1;
    }
private:
    int f1;
    int f2;
};

int main()
{
    std::vector<int> v;
    std::generate_n(std::back_inserter(v), 10, fibonacci());

    std::cout << v << std::endl;

    // Example of using pretty_ostream_iterator directly
    std::generate_n(pretty_ostream_iterator<int>(std::cout, ";"), 20, fibonacci());
    std::cout << std::endl;
}

Как и версия Марсело, он использует свойство типа is_container, которое должно быть специализировано для всех поддерживаемых контейнеров. Возможно, можно будет использовать черту для проверки value_type, const_iterator, _4 _ / _ 5_, но я не уверен, что рекомендовал бы это, поскольку он может соответствовать вещам, которые соответствуют этим критериям, но на самом деле не являются контейнерами, например std::basic_string. Также, как и версия Марсело, он использует шаблоны, которые можно настроить для определения используемых разделителей.

Основное отличие состоит в том, что я построил свою версию на основе pretty_ostream_iterator, которая работает аналогично std::ostream_iterator, но не печатает разделитель после последнего элемента. Форматирование контейнеров выполняется с помощью print_container_helper, который можно использовать непосредственно для печати контейнеров без признака is_container или для указания другого типа разделителей.

Я также определил is_container и разделители, поэтому он будет работать для контейнеров с нестандартными предикатами или распределителями, а также для char и wchar_t. Сама функция operator ‹< также определена для работы с потоками char и wchar_t.

Наконец, я использовал std::enable_if, который доступен как часть C ++ 0x и работает в Visual C ++ 2010 и g ++ 4.3 (требуется флаг -std = c ++ 0x) и более поздних версиях. Таким образом, нет зависимости от Boost.

person Sven    schedule 05.06.2011
comment
Если я правильно это понимаю, для того, чтобы пара печатала как <i, j> в одной функции и как [i j] в другой, вам нужно определить совершенно новый тип с горсткой статических членов, чтобы передать этот тип print_container_helper? Это кажется слишком сложным. Почему бы не использовать реальный объект с полями, которые можно настраивать для каждого конкретного случая, а специализации просто предоставляют разные значения по умолчанию? - person Dennis Zickefoose; 05.06.2011
comment
Посмотрите на это так: если есть куча разделителей, которые вам нравятся лично, вы можете создать пару классов со статическими членами раз и навсегда, а затем просто использовать их. Конечно, вы правы, что использование print_container_helper не так элегантно, как просто operator<<. Конечно, вы всегда можете изменить источник или просто добавить явные специализации для вашего любимого контейнера, например для pair<int, int> и для pair<double, string>. В конечном итоге это вопрос соотношения мощности и удобства. Предложения по улучшению приветствуются! - person Kerrek SB; 06.06.2011
comment
... и в дальнейшем, если вам уже нужна ситуативная печать того же типа данных в разных форматах, вам, вероятно, все равно придется написать хотя бы одну небольшую оболочку. Это не библиотека форматирования с широкими возможностями настройки, а, скорее, разумная библиотека по умолчанию с нулевыми усилиями, которая волшебным образом позволяет печатать контейнеры, не задумываясь ... (Но если вам нужна более глобальная гибкость, мы могли бы возможно, добавьте несколько макросов #macros, чтобы упростить работу со значениями по умолчанию.) - person Kerrek SB; 06.06.2011
comment
Реальная проблема заключается в том, что, хотя я мог легко изменить print_container_helper для использования параметров для настраиваемых разделителей, на самом деле нет никакого способа указать разделители для внутреннего контейнера (или пары), кроме специализации шаблона разделителей. Достичь этого было бы очень сложно. - person Sven; 06.06.2011
comment
Мне почти удалось найти удобное решение с настраиваемым разделителем, используя стирание типа. Если у вас уже есть класс разделителя MyDels, я могу сказать std::cout << CustomPrinter<MyDels>(x);. Что я не могу сделать в данный момент, так это сказать std::cout << CustomDelims<"{", ":", "}">(x);, потому что у вас не может быть const char * аргументов шаблона. Решение сделать разделители постоянными времени компиляции накладывает некоторые ограничения на простоту использования, но я думаю, что оно того стоит. - person Kerrek SB; 06.06.2011
comment
(Но, конечно, вы правы в том, что не так просто вложить пользовательские разделители. Я думаю, что простая специализация желаемых типов должна быть достаточно простой в этом сценарии, и, опять же, если вам нужна супер-настраиваемая печать , вы, вероятно, все равно будете писать свои собственные циклы. Это просто удобная библиотека для быстрой проверки.) - person Kerrek SB; 06.06.2011
comment
Интерфейс разделителя, который я визуализировал, включал шаблонный фабричный класс, который предоставляет значения по умолчанию. Пользователи специализируются на заводе по своему усмотрению. Вспомогательная функция принимает фактический объект-разделитель с параметром по умолчанию, который просто запрашивает эту фабрику. Затем вы можете написать базовый манипулятор для предоставления настраиваемого набора разделителей. Таким образом, вы получаете cout << v;, cout << delims("(", "-", ")") << v; или cout << pretty_container_helper(v, delims("(", "-", ")");. Таким образом, сам объект-разделитель не обязательно должен быть шаблоном, и все остается относительно простым. - person Dennis Zickefoose; 06.06.2011
comment
@Kerrek: ideone.com/aTHOQ ‹- Пример того, что я имею в виду. Основываясь на коде в этом ответе, а не на том, который указан в вопросе. Так что никаких пространств имен и так далее. Я не реализовал манипулятор, потому что правильно реализовать манипуляторы слишком сложно для чего-то, что я взломал вместе в качестве доказательства концепции. - person Dennis Zickefoose; 06.06.2011
comment
@Dennis: Спасибо за код. Насколько я могу судить, вы делаете ограничители константами объектной области, а не константами времени компиляции (т. Е. Статическими константами класса), верно? Таким образом, разница в том, что пользователи могут предоставлять разделители во время выполнения. Но проблема управления вложенным выводом остается, т.е. я не могу полностью настроить печать вложенного контейнера в каждом конкретном случае. (Я могу добавить в код ваши объектные разделители, если хотите. Но проверьте мою верхнюю публикацию, чтобы узнать последнюю версию.) - person Kerrek SB; 07.06.2011
comment
@Kerrek: У меня уже есть служебные функции, которые я использую для этой задачи, поэтому не меняйте код в моей учетной записи. Если вам не нравится дизайн, то это ваш выбор :-) - person Dennis Zickefoose; 07.06.2011
comment
Оказалось, что принятый ответ не был скомпилирован для меня с использованием gcc5.3 в нескольких угловых случаях. Например, в пространстве имен с двумя несвязанными структурами, но с собственным оператором ‹< (), приводил к ошибкам. ОДНАКО код из общедоступного GitHub, ссылка на который теперь приведена в начале этого вопроса, работал безупречно. Удалите этот комментарий, чтобы в конечном итоге сэкономить другим часам их жизни, пытаясь найти причину ошибки в принятом ответе. - person BaCh; 04.05.2017

В C ++ 11 теперь можно использовать цикл for на основе диапазона:

for (auto const& c : path)
    std::cout << c << ' ';
person Shoe    schedule 10.12.2013
comment
Это отлично работает, только если размер вектора не изменяется в теле цикла range for. - person Brian; 22.12.2014
comment
@BrianP. Ага. Печать элементов контейнера не изменяет диапазон контейнера. - person Shoe; 22.12.2014
comment
Что здесь предпочтительнее - c как копия значения или как константная ссылка, чтобы избежать копирования элемента? - person kleinfreund; 17.05.2015
comment
@kleinfreund Это зависит от содержимого вектора. Например, для вектора chars есть вероятность, что передача по постоянной ссылке на самом деле дороже, чем по значению. Но здесь мы говорим о супермикрооптимизациях. - person Shoe; 17.05.2015

Я думаю, что лучший способ сделать это - просто перегрузить operator<<, добавив эту функцию в свою программу:

#include <vector>
using std::vector;
#include <iostream>
using std::ostream;

template<typename T>
ostream& operator<< (ostream& out, const vector<T>& v) {
    out << "{";
    size_t last = v.size() - 1;
    for(size_t i = 0; i < v.size(); ++i) {
        out << v[i];
        if (i != last) 
            out << ", ";
    }
    out << "}";
    return out;
}

Затем вы можете использовать оператор << для любого возможного вектора, предполагая, что для его элементов также определено ostream& operator<<:

vector<string>  s = {"first", "second", "third"};
vector<bool>    b = {true, false, true, false, false};
vector<int>     i = {1, 2, 3, 4};
cout << s << endl;
cout << b << endl;
cout << i << endl;

Выходы:

{first, second, third}
{1, 0, 1, 0, 0}
{1, 2, 3, 4}
person Chris Redford    schedule 30.04.2014
comment
Сохранение v.size () - 1 как int - возможная потеря точности. Я исправил это в принятой рецензируемой редакции (stackoverflow.com/revisions/23397700/5), но впоследствии она была отредактирована снова восстанавливая возможную потерю точности. Думаю, на практике это не имеет большого значения, поскольку векторы обычно не такие большие. - person JDiMatteo; 09.10.2014
comment
Если вы не сохраните его как переменную, это ухудшит читаемость кода, что является частью вашего редактирования, с которым я не согласен. Я изменил тип last на size_t. - person Chris Redford; 09.10.2014
comment
size_t last = v.size() - 1; выглядит лишним, вы можете использовать if (i) out << ", "; условие перед out << v[i]; ссылкой - person Vladimir Gamalyan; 30.07.2016
comment
Этот оператор не найден ADL, поскольку он не входит в пространство имен ни одного из его аргументов. Таким образом, он будет скрыт operator<< любого другого пространства имен. Пример - person M.M; 02.02.2017
comment
Если вы собираетесь это сделать, зачем проверять if (i != last) каждый раз в цикле? Вместо этого, если контейнер не пуст, то (a) отправьте первый элемент, а затем (b) отправьте цикл оставшихся элементов, распечатав разделитель first (как префикс). Проверка внутреннего цикла (кроме самого условия цикла) не требуется. Требуется только один тест вне цикла. - person WhozCraig; 09.11.2019
comment
@WhozCraig Не стесняйтесь предлагать правку. Если он короче, то я согласен, что это может быть более элегантный код. Однако с вычислительной точки зрения это не меняет временную сложность O (n). - person Chris Redford; 09.11.2019

Вы можете печатать контейнеры, а также диапазоны и кортежи, используя библиотеку {fmt}. Например:

#include <vector>
#include <fmt/ranges.h>

int main() {
  auto v = std::vector<int>{1, 2, 3};
  fmt::print("{}", v);
}

отпечатки

{1, 2, 3}

на stdout (godbolt).

Я бы не рекомендовал перегружать operator<< для стандартных контейнеров, потому что это может привести к нарушениям ODR.

Заявление об ограничении ответственности: я являюсь автором {fmt}.

person vitaut    schedule 20.03.2019
comment
А что насчет std :: map? Я ничего не нашел в документации - person Karthik Nishanth; 21.12.2020
comment
Поддерживается форматирование всех контейнеров. - person vitaut; 21.12.2020
comment
Не могли бы вы дать мне отправную точку? Мне трудно найти использование fmtlib с fmtlib print std::map в качестве поискового запроса. Прошу прощения, если это считается вопросом новичка или RTFM-подобным :) - person Karthik Nishanth; 22.12.2020
comment
Вот пример с картой: godbolt.org/z/EG7aoE. Как видите, разницы в использовании нет. - person vitaut; 22.12.2020
comment
О боже! Это потрясающе godbolt.org/z/h7qxba - person Karthik Nishanth; 24.12.2020

Как насчет for_each + лямбда-выражения:

#include <vector>
#include <algorithm>
// ...
std::vector<char> vec;
// ...
std::for_each(
              vec.cbegin(),
              vec.cend(),
              [] (const char c) {std::cout << c << " ";} 
              );
// ...

Конечно, на основе диапазона - это наиболее элегантное решение для этой конкретной задачи, но оно также дает много других возможностей.

Объяснение

Алгоритм for_each принимает диапазон ввода и вызываемый объект, вызывая этот объект для каждого элемента диапазона. Диапазон ввода определяется двумя итераторами. вызываемый объект может быть функцией, указателем на функцию, объектом класса, который перегружает () operator, или, как в данном случае, лямбда-выражением. Параметр этого выражения соответствует типу элементов вектора.

Прелесть этой реализации заключается в силе, которую вы получаете от лямбда-выражений - вы можете использовать этот подход для гораздо большего, чем просто печать вектора.

person Maxim Chetrusca    schedule 14.10.2014

Это редактировалось несколько раз, и мы решили вызвать основной класс, который обертывает коллекцию RangePrinter.

Это должно работать автоматически с любой коллекцией после того, как вы написали одноразовую operator<< перегрузку, за исключением того, что вам понадобится специальная перегрузка для карт для печати пары, и, возможно, вы захотите настроить там разделитель.

У вас также может быть специальная функция печати для использования в элементе вместо того, чтобы просто выводить его напрямую, что немного похоже на алгоритмы STL, позволяющие передавать пользовательские предикаты. С map вы можете использовать его таким образом, с настраиваемым принтером для std::pair.

Ваш принтер по умолчанию просто выведет его в поток.

Хорошо, поработаем на нестандартном принтере. Я изменю свой внешний класс на RangePrinter. Итак, у нас есть 2 итератора и несколько разделителей, но мы не настроили, как печатать фактические элементы.

struct DefaultPrinter
{
   template< typename T >
   std::ostream & operator()( std::ostream& os, const T& t ) const
   {
     return os << t;
   }

   // overload for std::pair
   template< typename K, typename V >
   std::ostream & operator()( std::ostream & os, std::pair<K,V> const& p)
   {
      return os << p.first << '=' << p.second;
   }
};

// some prototypes
template< typename FwdIter, typename Printer > class RangePrinter;

template< typename FwdIter, typename Printer > 
  std::ostream & operator<<( std::ostream &, 
        RangePrinter<FwdIter, Printer> const& );

template< typename FwdIter, typename Printer=DefaultPrinter >
class RangePrinter
{
    FwdIter begin;
    FwdIter end;
    std::string delim;
    std::string open;
    std::string close;
    Printer printer;

    friend std::ostream& operator<< <>( std::ostream&, 
         RangePrinter<FwdIter,Printer> const& );

public:
    RangePrinter( FwdIter b, FwdIter e, Printer p,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), printer( p ), open( o ), close( c )
    {
    } 

     // with no "printer" variable
    RangePrinter( FwdIter b, FwdIter e,
         std::string const& d, std::string const & o, std::string const& c )
      : begin( b ), end( e ), open( o ), close( c )
    {
    } 

};


template<typename FwdIter, typename Printer>
std::ostream& operator<<( std::ostream& os, 
          RangePrinter<FwdIter, Printer> const& range )
{
    const Printer & printer = range.printer;

    os << range.open;
    FwdIter begin = range.begin, end = range.end;

    // print the first item
    if (begin == end) 
    { 
      return os << range.close; 
    }

    printer( os, *begin );

    // print the rest with delim as a prefix
    for( ++begin; begin != end; ++begin )
    {
       os << range.delim;
       printer( os, *begin );
    }
    return os << range.close;
}

Теперь по умолчанию он будет работать для карт, если оба типа ключа и значения доступны для печати, и вы можете вставить свой собственный специальный принтер элементов, когда они не являются (как вы можете с любым другим типом), или если вы не хотите = в качестве разделителя.

Я перемещаю бесплатную функцию, чтобы создать их до конца:

Бесплатная функция (версия итератора) будет выглядеть примерно так, и у вас могут быть даже значения по умолчанию:

template<typename Collection>
RangePrinter<typename Collection::const_iterator> rangePrinter
    ( const Collection& coll, const char * delim=",", 
       const char * open="[", const char * close="]")
{
   return RangePrinter< typename Collection::const_iterator >
     ( coll.begin(), coll.end(), delim, open, close );
}

Затем вы можете использовать его для std::set

 std::cout << outputFormatter( mySet );

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

person CashCow    schedule 31.01.2011
comment
Понятно. Это похоже на идею Марсело Кантоса, не так ли? Постараюсь превратить это в рабочий пример, спасибо! - person Kerrek SB; 31.01.2011
comment
Я считаю, что это решение намного чище, чем у Марсело, и оно предлагает такую ​​же гибкость. Мне нравится аспект, когда нужно явно заключить вывод в вызов функции. Чтобы быть действительно крутым, вы можете добавить поддержку вывода ряда итераторов напрямую, чтобы я мог делать std::cout << outputFormatter(beginOfRange, endOfRange);. - person Björn Pollex; 31.01.2011
comment
@CashCow: у этого решения есть одна проблема: оно не работает с рекурсивными коллекциями (т.е. коллекциями коллекций). std::pair - это самый простой пример внутренней коллекции. - person Matthieu M.; 31.01.2011
comment
Мне очень нравится этот ответ, поскольку он не имеет зависимостей и не должен знать о поддерживаемых им контейнерах. Можем ли мы выяснить, легко ли он справляется с std::maps и работает ли он для коллекций коллекций? Однако я склонен принять это как ответ. Надеюсь, Марсело не против, его решение тоже работает. - person Kerrek SB; 31.01.2011
comment
@Matthieu M. Это зависит от того, как вы распечатываете внутреннюю коллекцию. Если вы просто используете os ‹< open ‹< * iter ‹< close, тогда у вас возникнут проблемы, но если вы позволите своему пользователю передать пользовательский принтер, как я предлагал, вы можете распечатать все, что захотите. - person CashCow; 31.01.2011
comment
О, можем ли мы объединить все это в одну универсальную функцию принтера? Для примитивных типов он просто выполняет ‹<, а для коллекций он рекурсивно вызывает себя для элементов? - person Kerrek SB; 31.01.2011
comment
Я познакомил вас с вашим индивидуальным принтером. Конечно, вы можете использовать функции для создания принтеров предметов. Вы также можете добавить частичную специализацию для вектора, но тогда вам придется пойти по пути специализации каждого из них по отдельности. Должно быть лучше иметь для этого принтер коллекции, и вы даже можете перегрузить operator () в RangePrinter, чтобы он соответствовал типу внутреннего принтера. - person CashCow; 31.01.2011
comment
@Kerrek. Если бы мы с Марсело занимались парным программированием, мы, вероятно, придумали бы здесь идеальное решение. Для этого вы должны использовать тип черт. У boost может быть что-то лучше, чем IsContainer Марсело, хотя он будет смотреть на черты типа, чтобы увидеть, действует ли он как контейнер. - person CashCow; 31.01.2011
comment
@Matthieu Я видел по крайней мере одно из ваших исправлений. И да, это более специализированная перегрузка, но имеет эффект, что если вы передадите std :: pair, это будет более близкое соответствие, поэтому компилятор выберет его вместо общего, что немного похоже на частичную специализацию. - person CashCow; 31.01.2011
comment
Если вы хотите что-то полностью рекурсивное, вам, скорее всего, придется использовать принтер и использовать умные методы черт SFINAE (если у элемента есть методы begin () и end (), это коллекция ... если это общий указатель, распечатайте то, на что он указывает к ... и т. д.), и это предполагает, что вы хотите использовать одни и те же разделители / открыватели / закрывающие элементы на каждом уровне, и вам также нужно будет решить, как использовать ключ-значение. Если есть большой интерес к этому, мы могли бы сделать это вики сообщества? Насколько точно вложены ваши коллекции? - person CashCow; 31.01.2011
comment
Вот мой пример программы, которая не компилируется: pastebin.com/Vx6hAWuL Я полагаю, что в реальной жизни вы обычно не имеют более одного вектора векторов или около того, поэтому, возможно, такая вложенность не так важна, как поддержка векторов пар и поддержка карт. - person Kerrek SB; 31.01.2011
comment
Вы внесли несколько модификаций, и я не знаю, что не компилируется. Завтра протестирую. Я не уверен, что для первого объявления шаблона также требуется параметр по умолчанию. Где именно он не компилируется? Какую ошибку вы видите? - person CashCow; 01.02.2011
comment
Мне очень жаль, я ошибся, когда обновил свой локальный код вашей новой версией. Вы правы, теперь работает лучше. Я обновил пример: pastebin.com/ewMHBMnB По-прежнему есть проблемы с вектором ‹вектор‹ ... >>, хотя... - person Kerrek SB; 01.02.2011
comment
Заметив, что RangePrinter неожиданно работает для std :: strings, я добавил поддержку массивов, как это сделал Марсело: pastebin.com / ZhBc0XhR Это позволяет вам сказать std::cout << outputFormatter("Hello World") << std::endl; - person Kerrek SB; 01.02.2011
comment
Еще один вопрос: нужно ли мне переписывать все определение RangePrinter, чтобы специализировать разделители, например, для std :: set? Можно ли как-то этого избежать? - person Kerrek SB; 01.02.2011
comment
Проблема в том, что у моего принтера один интерфейс, а у RangePrinter другой. RangePrinter - это объект, предназначенный для печати одного диапазона, и вы указываете, как вы печатаете то, что находится внутри. Вероятно, нам нужен CollectionPrinter, который имеет operator () и использует RangePrinter внизу. - person CashCow; 01.02.2011
comment
Я вижу проблему: мы должны отделить спецификацию разделителя и то, что печатается, то есть, что это за набор. Тогда было бы возможно, когда у вас есть vector ‹vector ‹T››, чтобы передать спецификацию разделителя каждому элементу внешнего вектора для печати внутренних векторов. - person CashCow; 01.02.2011
comment
Это было бы довольно серьезное повторное редактирование, но сейчас я вижу это следующим образом: разделители должны быть отдельной структурой с тремя строками. Нам нужен функтор принтера (RangeItemPrinter), который принимает разделители в качестве параметра и сохраняет состояние, зная, печатает ли он первый объект, и нам нужен другой класс для фактической печати элементов (независимо от того, находятся ли они в диапазоне), который является принтером = DefaultPrinter здесь. RangePrinter - это просто оболочка для всех этих классов, которая передается в потоковом режиме с помощью оператора ‹<. Вы можете создать принтер для объектов, чтобы распечатать внутреннюю структуру вектора ‹vector› из существующих классов. - person CashCow; 01.02.2011

Вот рабочая библиотека, представленная как законченная рабочая программа, которую я только что взломал:

#include <set>
#include <vector>
#include <iostream>

#include <boost/utility/enable_if.hpp>

// Default delimiters
template <class C> struct Delims { static const char *delim[3]; };
template <class C> const char *Delims<C>::delim[3]={"[", ", ", "]"};
// Special delimiters for sets.                                                                                                             
template <typename T> struct Delims< std::set<T> > { static const char *delim[3]; };
template <typename T> const char *Delims< std::set<T> >::delim[3]={"{", ", ", "}"};

template <class C> struct IsContainer { enum { value = false }; };
template <typename T> struct IsContainer< std::vector<T> > { enum { value = true }; };
template <typename T> struct IsContainer< std::set<T>    > { enum { value = true }; };

template <class C>
typename boost::enable_if<IsContainer<C>, std::ostream&>::type
operator<<(std::ostream & o, const C & x)
{
  o << Delims<C>::delim[0];
  for (typename C::const_iterator i = x.begin(); i != x.end(); ++i)
    {
      if (i != x.begin()) o << Delims<C>::delim[1];
      o << *i;
    }
  o << Delims<C>::delim[2];
  return o;
}

template <typename T> struct IsChar { enum { value = false }; };
template <> struct IsChar<char> { enum { value = true }; };

template <typename T, int N>
typename boost::disable_if<IsChar<T>, std::ostream&>::type
operator<<(std::ostream & o, const T (&x)[N])
{
  o << "[";
  for (int i = 0; i != N; ++i)
    {
      if (i) o << ",";
      o << x[i];
    }
  o << "]";
  return o;
}

int main()
{
  std::vector<int> i;
  i.push_back(23);
  i.push_back(34);

  std::set<std::string> j;
  j.insert("hello");
  j.insert("world");

  double k[] = { 1.1, 2.2, M_PI, -1.0/123.0 };

  std::cout << i << "\n" << j << "\n" << k << "\n";
}

В настоящее время он работает только с vector и set, но его можно заставить работать с большинством контейнеров, просто расширив специализации IsContainer. Я не особо задумывался о том, минимален ли этот код, но не могу сразу придумать что-либо, что можно было бы исключить как избыточное.

РЕДАКТИРОВАТЬ: Просто для удовольствия я включил версию, которая обрабатывает массивы. Мне пришлось исключить массивы символов, чтобы избежать дальнейших двусмысленностей; у него все еще могут быть проблемы с wchar_t[].

person Marcelo Cantos    schedule 31.01.2011
comment
@Nawaz: Как я уже сказал, это только начало решения. Вы можете поддержать std::map<> либо путем специализации оператора, либо путем определения operator<< для std::pair<>. - person Marcelo Cantos; 31.01.2011
comment
Однако +1 за использование шаблона класса Delims! - person Nawaz; 31.01.2011
comment
@MC: Ой, хорошо. Это выглядит очень многообещающе! (Кстати, вам нужен возвращаемый тип std :: ostream &, я изначально об этом забыл.) - person Kerrek SB; 31.01.2011
comment
Хм, у меня неоднозначная перегрузка, когда я пробую это на std :: vector ‹int› и std :: set ‹std :: string› ... - person Kerrek SB; 31.01.2011
comment
Да, в настоящее время я пытаюсь понять, как предотвратить двусмысленность, вызванную тем, что шаблон operator<< соответствует практически чему угодно. - person Marcelo Cantos; 31.01.2011
comment
Уф, это было испытание! Я бросил полотенце на выяснение SFINAE (меня бьет каждый раз, черт возьми) и просто использовал _ 1_, который скрывает всю грязь SFINAE за простым шаблоном. - person Marcelo Cantos; 31.01.2011
comment
Хорошо, это работает - так каковы требования? Boost / utility.hpp, а каждый допустимый контейнер нужно добавить в специализации IsContainer? Что ж, это, безусловно, отличное решение, которое можно где-то упаковать в один файл. Спасибо! - person Kerrek SB; 31.01.2011
comment
Я только что добавил код для обработки T[N]. Я мог бы, вероятно, объединить два оператора в один, но это могло бы фактически увеличить общий размер кода с незначительной выгодой. - person Marcelo Cantos; 31.01.2011

Код оказался удобным уже несколько раз, и я чувствую затраты на настройку, поскольку его использование довольно мало. Таким образом, я решил выпустить его под лицензией MIT и предоставить репозиторий GitHub, куда можно загрузить заголовок и небольшой файл примера.

http://djmuw.github.io/prettycc/

0. Предисловие и формулировка

«Украшение» с точки зрения этого ответа - это набор префиксов, разделителей и постфиксов. Где строка префикса вставляется в поток до, а строка постфикса после значений контейнера (см. 2. Целевые контейнеры). Строка-разделитель вставляется между значениями соответствующего контейнера.

Примечание. На самом деле этот ответ не дает ответа на вопрос на 100%, поскольку оформление не является строго компилируемой постоянной времени, поскольку во время выполнения требуются проверки, чтобы проверить, применено ли настраиваемое оформление к текущему потоку. Тем не менее , Я думаю, у него есть достойные возможности.

Примечание 2: могут быть незначительные ошибки, так как он еще недостаточно хорошо протестирован.

1. Общая идея / использование

Нулевой дополнительный код, необходимый для использования

Это должно быть так просто, как

#include <vector>
#include "pretty.h"

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints 1, 2, 3, 4, 5
  return 0;
}

Легкая настройка ...

... по отношению к конкретному объекту потока

#include <vector>
#include "pretty.h"

int main()
{
  // set decoration for std::vector<int> for cout object
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

или в отношении всех потоков:

#include <vector>
#include "pretty.h"

// set decoration for std::vector<int> for all ostream objects
PRETTY_DEFAULT_DECORATION(std::vector<int>, "{", ", ", "}")

int main()
{
  std::cout << std::vector<int>{1,2,3,4,5}; // prints {1, 2, 3, 4, 5}
  std::cout << pretty::decoration<std::vector<int>>("(", ",", ")");
  std::cout << std::vector<int>{1,2,3,4,5}; // prints (1,2,3,4,5)
  return 0;
}

Примерное описание

  • Код включает шаблон класса, обеспечивающий оформление по умолчанию для любого типа.
  • который может быть специализирован для изменения декорации по умолчанию для (а) определенного типа (ов), и это
  • используя частное хранилище, предоставленное ios_base, используя _5 _ / _ 6_, чтобы сохранить указатель на объект pretty::decor, специально украшающий определенный тип в определенном потоке.

Если объект pretty::decor<T> для этого потока не был настроен явно, вызывается pretty::defaulted<T, charT, chartraitT>::decoration(), чтобы получить оформление по умолчанию для данного типа. Класс pretty::defaulted должен быть специализирован для настройки декораций по умолчанию.

2. Целевые объекты / контейнеры

Целевые объекты obj для "красивого украшения" этого кода - это объекты, имеющие либо

  • определены перегрузки std::begin и std::end (включая массивы в стиле C),
  • имея begin(obj) и end(obj), доступные через ADL,
  • относятся к типу std::tuple
  • или типа std::pair.

Код включает признак для идентификации классов с признаками диапазона (_18 _ / _ 19_). (Тем не менее, здесь нет проверки, является ли begin(obj) == end(obj) допустимым выражением.)

Код предоставляет operator<<s в глобальном пространстве имен, которые применяются только к классам, не имеющим более специализированной версии operator<<. Поэтому, например, std::string не печатается с использованием оператора в этом коде, хотя имеет действительную пару _24 _ / _ 25_.

3. Использование и настройка

Декорации можно накладывать отдельно для каждого типа (кроме разных tuples) и потока (не для типа потока!). (То есть std::vector<int> может иметь разные декорации для разных потоковых объектов.)

А) украшение по умолчанию

Префикс по умолчанию - "" (ничего), как и постфикс по умолчанию, а разделитель по умолчанию - ", " (запятая + пробел).

Б) Настроенное оформление по умолчанию для типа путем специализации шаблона класса pretty::defaulted

struct defaulted имеет статическую функцию-член decoration(), возвращающую объект decor, который включает значения по умолчанию для данного типа.

Пример использования массива:

Настройте печать массива по умолчанию:

namespace pretty
{
  template<class T, std::size_t N>
  struct defaulted<T[N]>
  {
    static decor<T[N]> decoration()
    {
      return{ { "(" }, { ":" }, { ")" } };
    }
  };
}

Распечатайте массив arry:

float e[5] = { 3.4f, 4.3f, 5.2f, 1.1f, 22.2f };
std::cout << e << '\n'; // prints (3.4:4.3:5.2:1.1:22.2)

Использование макроса PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) для char потоков

Макрос расширяется до

namespace pretty { 
  template< __VA_ARGS__ >
  struct defaulted< TYPE > {
    static decor< TYPE > decoration() {
      return { PREFIX, DELIM, POSTFIX };
    } 
  }; 
} 

позволяя переписать вышеупомянутую частичную специализацию на

PRETTY_DEFAULT_DECORATION(T[N], "", ";", "", class T, std::size_t N)

или вставив полную специализацию, например

PRETTY_DEFAULT_DECORATION(std::vector<int>, "(", ", ", ")")

Включен еще один макрос для wchar_t потоков: PRETTY_DEFAULT_WDECORATION.

В) Наложить украшение на ручьи

Функция pretty::decoration используется для наложения украшения на определенный поток. Существуют перегрузки, принимающие либо - один строковый аргумент является разделителем (принимая префикс и постфикс из класса по умолчанию) - либо три строковых аргумента, собирающих полное оформление

Полное оформление для данного типа и потока

float e[3] = { 3.4f, 4.3f, 5.2f };
std::stringstream u;
// add { ; } decoration to u
u << pretty::decoration<float[3]>("{", "; ", "}");

// use { ; } decoration
u << e << '\n'; // prints {3.4; 4.3; 5.2}

// uses decoration returned by defaulted<float[3]>::decoration()
std::cout << e; // prints 3.4, 4.3, 5.2

Настройка разделителя для данного потока

PRETTY_DEFAULT_DECORATION(float[3], "{{{", ",", "}}}")

std::stringstream v;
v << e; // prints {{{3.4,4.3,5.2}}}

v << pretty::decoration<float[3]>(":");
v << e; // prints {{{3.4:4.3:5.2}}}

v << pretty::decoration<float[3]>("((", "=", "))");
v << e; // prints ((3.4=4.3=5.2))

4. Особое обращение с std::tuple

Вместо того, чтобы разрешать специализацию для каждого возможного типа кортежа, этот код применяет любое украшение, доступное для std::tuple<void*>, ко всем видам std::tuple<...>.

5. Удалите персонализированное оформление из потока.

Чтобы вернуться к оформлению по умолчанию для данного типа, используйте pretty::clear шаблон функции в потоке s.

s << pretty::clear<std::vector<int>>();

5. Дополнительные примеры

Печать "в виде матрицы" с разделителем новой строки

std::vector<std::vector<int>> m{ {1,2,3}, {4,5,6}, {7,8,9} };
std::cout << pretty::decoration<std::vector<std::vector<int>>>("\n");
std::cout << m;

Отпечатки

1, 2, 3
4, 5, 6
7, 8, 9

Смотрите на ideone / KKUebZ

6. Код

#ifndef pretty_print_0x57547_sa4884X_0_1_h_guard_
#define pretty_print_0x57547_sa4884X_0_1_h_guard_

#include <string>
#include <iostream>
#include <type_traits>
#include <iterator>
#include <utility>

#define PRETTY_DEFAULT_DECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE > {\
    static decor< TYPE > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

#define PRETTY_DEFAULT_WDECORATION(TYPE, PREFIX, DELIM, POSTFIX, ...) \
    namespace pretty { template< __VA_ARGS__ >\
    struct defaulted< TYPE, wchar_t, std::char_traits<wchar_t> > {\
    static decor< TYPE, wchar_t, std::char_traits<wchar_t> > decoration(){\
      return { PREFIX, DELIM, POSTFIX };\
    } /*decoration*/ }; /*defaulted*/} /*pretty*/

namespace pretty
{

  namespace detail
  {
    // drag in begin and end overloads
    using std::begin;
    using std::end;
    // helper template
    template <int I> using _ol = std::integral_constant<int, I>*;
    // SFINAE check whether T is a range with begin/end
    template<class T>
    class is_range
    {
      // helper function declarations using expression sfinae
      template <class U, _ol<0> = nullptr>
      static std::false_type b(...);
      template <class U, _ol<1> = nullptr>
      static auto b(U &v) -> decltype(begin(v), std::true_type());
      template <class U, _ol<0> = nullptr>
      static std::false_type e(...);
      template <class U, _ol<1> = nullptr>
      static auto e(U &v) -> decltype(end(v), std::true_type());
      // return types
      using b_return = decltype(b<T>(std::declval<T&>()));
      using e_return = decltype(e<T>(std::declval<T&>()));
    public:
      static const bool value = b_return::value && e_return::value;
    };
  }

  // holder class for data
  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct decor
  {
    static const int xindex;
    std::basic_string<CharT, TraitT> prefix, delimiter, postfix;
    decor(std::basic_string<CharT, TraitT> const & pre = "",
      std::basic_string<CharT, TraitT> const & delim = "",
      std::basic_string<CharT, TraitT> const & post = "")
      : prefix(pre), delimiter(delim), postfix(post) {}
  };

  template<class T, class charT, class traits>
  int const decor<T, charT, traits>::xindex = std::ios_base::xalloc();

  namespace detail
  {

    template<class T, class CharT, class TraitT>
    void manage_decor(std::ios_base::event evt, std::ios_base &s, int const idx)
    {
      using deco_type = decor<T, CharT, TraitT>;
      if (evt == std::ios_base::erase_event)
      { // erase deco
        void const * const p = s.pword(idx);
        if (p)
        {
          delete static_cast<deco_type const * const>(p);
          s.pword(idx) = nullptr;
        }
      }
      else if (evt == std::ios_base::copyfmt_event)
      { // copy deco
        void const * const p = s.pword(idx);
        if (p)
        {
          auto np = new deco_type{ *static_cast<deco_type const * const>(p) };
          s.pword(idx) = static_cast<void*>(np);
        }
      }
    }

    template<class T> struct clearer {};

    template<class T, class CharT, class TraitT>
    std::basic_ostream<CharT, TraitT>& operator<< (
      std::basic_ostream<CharT, TraitT> &s, clearer<T> const &)
    {
      using deco_type = decor<T, CharT, TraitT>;
      void const * const p = s.pword(deco_type::xindex);
      if (p)
      { // delete if set
        delete static_cast<deco_type const *>(p);
        s.pword(deco_type::xindex) = nullptr;
      }
      return s;
    }

    template <class CharT> 
    struct default_data { static const CharT * decor[3]; };
    template <> 
    const char * default_data<char>::decor[3] = { "", ", ", "" };
    template <> 
    const wchar_t * default_data<wchar_t>::decor[3] = { L"", L", ", L"" };

  }

  // Clear decoration for T
  template<class T>
  detail::clearer<T> clear() { return{}; }
  template<class T, class CharT, class TraitT>
  void clear(std::basic_ostream<CharT, TraitT> &s) { s << detail::clearer<T>{}; }

  // impose decoration on ostream
  template<class T, class CharT, class TraitT>
  std::basic_ostream<CharT, TraitT>& operator<<(
    std::basic_ostream<CharT, TraitT> &s, decor<T, CharT, TraitT> && h)
  {
    using deco_type = decor<T, CharT, TraitT>;
    void const * const p = s.pword(deco_type::xindex);
    // delete if already set
    if (p) delete static_cast<deco_type const *>(p);
    s.pword(deco_type::xindex) = static_cast<void *>(new deco_type{ std::move(h) });
    // check whether we alread have a callback registered
    if (s.iword(deco_type::xindex) == 0)
    { // if this is not the case register callback and set iword
      s.register_callback(detail::manage_decor<T, CharT, TraitT>, deco_type::xindex);
      s.iword(deco_type::xindex) = 1;
    }
    return s;
  }

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  struct defaulted
  {
    static inline decor<T, CharT, TraitT> decoration()
    {
      return{ detail::default_data<CharT>::decor[0],
        detail::default_data<CharT>::decor[1],
        detail::default_data<CharT>::decor[2] };
    }
  };

  template<class T, class CharT = char, class TraitT = std::char_traits<CharT>>
  decor<T, CharT, TraitT> decoration(
    std::basic_string<CharT, TraitT> const & prefix,
    std::basic_string<CharT, TraitT> const & delimiter,
    std::basic_string<CharT, TraitT> const & postfix)
  {
    return{ prefix, delimiter, postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(
      std::basic_string<CharT, TraitT> const & delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      delimiter, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const prefix,
      CharT const * const delimiter, CharT const * const postfix)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ str_type{ prefix }, str_type{ delimiter }, str_type{ postfix } };
  }

  template<class T, class CharT = char,
  class TraitT = std::char_traits < CharT >>
    decor<T, CharT, TraitT> decoration(CharT const * const delimiter)
  {
    using str_type = std::basic_string<CharT, TraitT>;
    return{ defaulted<T, CharT, TraitT>::decoration().prefix,
      str_type{ delimiter }, defaulted<T, CharT, TraitT>::decoration().postfix };
  }

  template<typename T, std::size_t N, std::size_t L>
  struct tuple
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &delimiter)
    {
      s << std::get<N>(value) << delimiter;
      tuple<T, N + 1, L>::print(s, value, delimiter);
    }
  };

  template<typename T, std::size_t N>
  struct tuple<T, N, N>
  {
    template<class CharT, class TraitT>
    static void print(std::basic_ostream<CharT, TraitT>& s, T const & value,
      std::basic_string<CharT, TraitT> const &) {
      s << std::get<N>(value);
    }
  };

}

template<class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class CharT, class TraitT, class ... T>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::tuple<T...> const & v)
{
  using deco_type = pretty::decor<std::tuple<void*>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::tuple<void*>, CharT, TraitT>;
  using pretty_tuple = pretty::tuple<std::tuple<T...>, 0U, sizeof...(T)-1U>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  pretty_tuple::print(s, v, d ? d->delimiter : 
    defaulted_type::decoration().delimiter);
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}

template<class T, class U, class CharT, class TraitT>
std::basic_ostream<CharT, TraitT> & operator<< (
  std::basic_ostream<CharT, TraitT> &s, std::pair<T, U> const & v)
{
  using deco_type = pretty::decor<std::pair<T, U>, CharT, TraitT>;
  using defaulted_type = pretty::defaulted<std::pair<T, U>, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto const d = static_cast<deco_type const * const>(p);
  s << (d ? d->prefix : defaulted_type::decoration().prefix);
  s << v.first;
  s << (d ? d->delimiter : defaulted_type::decoration().delimiter);
  s << v.second;
  s << (d ? d->postfix : defaulted_type::decoration().postfix);
  return s;
}


template<class T, class CharT = char,
class TraitT = std::char_traits < CharT >>
  typename std::enable_if < pretty::detail::is_range<T>::value,
  std::basic_ostream < CharT, TraitT >> ::type & operator<< (
    std::basic_ostream<CharT, TraitT> &s, T const & v)
{
  bool first(true);
  using deco_type = pretty::decor<T, CharT, TraitT>;
  using default_type = pretty::defaulted<T, CharT, TraitT>;
  void const * const p = s.pword(deco_type::xindex);
  auto d = static_cast<pretty::decor<T, CharT, TraitT> const * const>(p);
  s << (d ? d->prefix : default_type::decoration().prefix);
  for (auto const & e : v)
  { // v is range thus range based for works
    if (!first) s << (d ? d->delimiter : default_type::decoration().delimiter);
    s << e;
    first = false;
  }
  s << (d ? d->postfix : default_type::decoration().postfix);
  return s;
}

#endif // pretty_print_0x57547_sa4884X_0_1_h_guard_
person Pixelchemist    schedule 27.09.2015

Просто скопируйте контейнер в консоль.

std::vector<int> v{1,2,3,4};
std::copy(v.begin(),v.end(),std::ostream_iterator<int>(std::cout, " " ));

Должен выводить:

1 2 3 4
person g24l    schedule 07.04.2015

Использование std::copy, но без дополнительного завершающего разделителя

Альтернативный / модифицированный подход с использованием std::copy (как первоначально использовалось в @JoshuaKravtiz answer), но без добавления дополнительного конечного разделителя после последнего элемента:

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

template <typename T>
void print_contents(const std::vector<T>& v, const char * const separator = " ")
{
    if(!v.empty())
    {
        std::copy(v.begin(),
                  --v.end(),
                  std::ostream_iterator<T>(std::cout, separator));
        std::cout << v.back() << "\n";
    }
}

// example usage
int main() {
    std::vector<int> v{1, 2, 3, 4};
    print_contents(v);      // '1 2 3 4'
    print_contents(v, ":"); // '1:2:3:4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // '1'
    return 0;
}

Пример использования применительно к контейнеру настраиваемого типа POD:

// includes and 'print_contents(...)' as above ...

class Foo
{
    int i;
    friend std::ostream& operator<<(std::ostream& out, const Foo& obj);
public:
    Foo(const int i) : i(i) {}
};

std::ostream& operator<<(std::ostream& out, const Foo& obj)
{
    return out << "foo_" << obj.i; 
}

int main() {
    std::vector<Foo> v{1, 2, 3, 4};
    print_contents(v);      // 'foo_1 foo_2 foo_3 foo_4'
    print_contents(v, ":"); // 'foo_1:foo_2:foo_3:foo_4'
    v = {};
    print_contents(v);      // ... no std::cout
    v = {1};
    print_contents(v);      // 'foo_1'
    return 0;
}
person dfrib    schedule 30.07.2017

Проблема, вероятно, в предыдущем цикле:

(x = 17; isalpha(firstsquare); x++)

Этот цикл не будет выполняться вообще (если firstsquare не алфавитный) или будет выполняться вечно (если он алфавитный). Причина в том, что firstsquare не изменяется при увеличении x.

person MSalters    schedule 25.05.2012

В C ++ 11 хорошим решением может быть цикл for на основе диапазона:

vector<char> items = {'a','b','c'};
for (char n : items)
    cout << n << ' ';

Выход:

a b c 
person stupidgoddamnjerk    schedule 12.03.2015

оператор перегрузки ‹<:

template<typename OutStream, typename T>
OutStream& operator<< (OutStream& out, const vector<T>& v)
{
    for (auto const& tmp : v)
        out << tmp << " ";
    out << endl;
    return out;
}

Использование:

vector <int> test {1,2,3};
wcout << test; // or any output stream
person ivanmara    schedule 12.08.2016

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

Основные сведения см. здесь

По сути, вы делаете следующее:

  1. Создайте класс, производный от std::locale::facet. Небольшой недостаток заключается в том, что вам понадобится блок компиляции где-то для хранения его идентификатора. Назовем его MyPrettyVectorPrinter. Вы, вероятно, дадите ему лучшее имя, а также создадите их для пары и карты.
  2. В своей функции потока вы проверяете std::has_facet< MyPrettyVectorPrinter >
  3. Если это вернет истину, извлеките его с помощью std::use_facet< MyPrettyVectorPrinter >( os.getloc() )
  4. У ваших фасетных объектов будут значения разделителей, и вы сможете их прочитать. Если фасет не найден, ваша функция печати (operator<<) предоставит фасет по умолчанию. Обратите внимание, что вы можете сделать то же самое для чтения вектора.

Мне нравится этот метод, потому что вы можете использовать печать по умолчанию, в то же время сохраняя возможность использовать настраиваемое переопределение.

Недостатками являются необходимость библиотеки для вашего аспекта при использовании в нескольких проектах (поэтому не может быть только заголовок), а также тот факт, что вам нужно остерегаться затрат на создание нового объекта языкового стандарта.

Я написал это как новое решение, а не модифицировал другое, потому что считаю, что оба подхода могут быть правильными, и вы делаете свой выбор.

person Community    schedule 13.02.2013
comment
Позвольте мне прямо сказать: при таком подходе мне нужно активно вносить в белый список каждый тип контейнера, который я хочу использовать? - person Kerrek SB; 14.02.2013
comment
На самом деле не следует расширять std, кроме как для своих собственных типов, но вы пишете перегрузку оператора ‹< для каждого типа контейнера (вектор, карта, список, двухсторонняя очередь) плюс пара, которую вы хотите распечатать. Конечно, некоторые из них могут иметь общий аспект (например, вы можете захотеть распечатать список, вектор и deque все равно). Вы предоставляете метод печати по умолчанию, но позволяете пользователям создавать фасет, языковой стандарт и наполнение перед печатью. Немного похоже на то, как boost печатает их date_time. Можно также загрузить их фасет в глобальный языковой стандарт, чтобы печатать таким образом по умолчанию. - person CashCow; 19.02.2013

Этот ответ основан на ответе Zorawar, но я не мог оставить там комментарий.

Вы можете сделать auto (C ++ 11) / _ 2_ версию const, используя вместо этого cbegin и cend

for (auto i = path.cbegin(); i != path.cend(); ++i)
    std::cout << *i << ' ';
person SketchyManDan    schedule 07.02.2017

Я вижу две проблемы. Как указано в

for (x = 17; isalpha(firstsquare); x++)

либо есть бесконечный цикл, либо он не выполняется вообще, а также в if (entrance == 'S'), если входной символ отличается от 'S', тогда в вектор пути ничего не помещается, что делает его пустым и, таким образом, ничего не выводит на экран. Вы можете протестировать последнее, проверив path.empty() или распечатав path.size().

В любом случае, не лучше ли использовать строку вместо вектора? Вы также можете получить доступ к содержимому строки, например к массиву, искать символы, извлекать подстроки и легко печатать строку (без цикла).

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

person miguelbernadi    schedule 25.05.2012

Цель здесь - использовать ADL для настройки того, как мы красиво печатаем.

Вы передаете тег средства форматирования и переопределяете 4 функции (до, после, между и по убыванию) в пространстве имен тега. Это изменяет способ, которым форматтер печатает «украшения» при итерации по контейнерам.

Форматировщик по умолчанию, который выполняет {(a->b),(c->d)} для карт, (a,b,c) для кортежей, "hello" для строк, [x,y,z] для всего остального.

Он должен «просто работать» со сторонними итеративными типами (и обращаться с ними как со «всем остальным»).

Если вам нужны индивидуальные украшения для сторонних итераций, просто создайте свой собственный тег. Чтобы обработать спуск карты, потребуется немного поработать (вам нужно перегрузить pretty_print_descend( your_tag, чтобы вернуть pretty_print::decorator::map_magic_tag<your_tag>). Возможно, есть более чистый способ сделать это, не уверен.

Небольшая библиотека для определения итеративности и кортежа:

namespace details {
  using std::begin; using std::end;
  template<class T, class=void>
  struct is_iterable_test:std::false_type{};
  template<class T>
  struct is_iterable_test<T,
    decltype((void)(
      (void)(begin(std::declval<T>())==end(std::declval<T>()))
      , ((void)(std::next(begin(std::declval<T>()))))
      , ((void)(*begin(std::declval<T>())))
      , 1
    ))
  >:std::true_type{};
  template<class T>struct is_tupleoid:std::false_type{};
  template<class...Ts>struct is_tupleoid<std::tuple<Ts...>>:std::true_type{};
  template<class...Ts>struct is_tupleoid<std::pair<Ts...>>:std::true_type{};
  // template<class T, size_t N>struct is_tupleoid<std::array<T,N>>:std::true_type{}; // complete, but problematic
}
template<class T>struct is_iterable:details::is_iterable_test<std::decay_t<T>>{};
template<class T, std::size_t N>struct is_iterable<T(&)[N]>:std::true_type{}; // bypass decay
template<class T>struct is_tupleoid:details::is_tupleoid<std::decay_t<T>>{};

template<class T>struct is_visitable:std::integral_constant<bool, is_iterable<T>{}||is_tupleoid<T>{}> {};

Библиотека, которая позволяет нам посещать содержимое объекта итеративного или кортежного типа:

template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto&& b = begin(c);
  auto&& e = end(c);
  if (b==e)
      return;
  std::forward<F>(f)(*b);
}
template<class C, class F>
std::enable_if_t<is_iterable<C>{}> visit_all_but_first(C&& c, F&& f) {
  using std::begin; using std::end;
  auto it = begin(c);
  auto&& e = end(c);
  if (it==e)
      return;
  it = std::next(it);
  for( ; it!=e; it = std::next(it) ) {
    f(*it);
  }
}

namespace details {
  template<class Tup, class F>
  void visit_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is, class Tup, class F>
  void visit_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    std::forward<F>(f)( std::get<0>( std::forward<Tup>(tup) ) );
  }
  template<class Tup, class F>
  void visit_all_but_first( std::index_sequence<>, Tup&&, F&& ) {}
  template<size_t... Is,class Tup, class F>
  void visit_all_but_first( std::index_sequence<0,Is...>, Tup&& tup, F&& f ) {
    int unused[] = {0,((void)(
      f( std::get<Is>(std::forward<Tup>(tup)) )
    ),0)...};
    (void)(unused);
  }
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_first(Tup&& tup, F&& f) {
  details::visit_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}
template<class Tup, class F>
std::enable_if_t<is_tupleoid<Tup>{}> visit_all_but_first(Tup&& tup, F&& f) {
  details::visit_all_but_first( std::make_index_sequence< std::tuple_size<std::decay_t<Tup>>{} >{}, std::forward<Tup>(tup), std::forward<F>(f) );
}

Симпатичная библиотека для печати:

namespace pretty_print {
  namespace decorator {
    struct default_tag {};
    template<class Old>
    struct map_magic_tag:Old {}; // magic for maps

    // Maps get {}s. Write trait `is_associative` to generalize:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('{');
    }

    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::map<Xs...> const& ) {
      s << CharT('}');
    }

    // tuples and pairs get ():
    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT('(');
    }

    template<class CharT, class Traits, class Tup >
    std::enable_if_t<is_tupleoid<Tup>{}> pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, Tup const& ) {
      s << CharT(')');
    }

    // strings with the same character type get ""s:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_before( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    template<class CharT, class Traits, class...Xs >
    void pretty_print_after( default_tag, std::basic_ostream<CharT, Traits>& s, std::basic_string<CharT, Xs...> const& ) {
      s << CharT('"');
    }
    // and pack the characters together:
    template<class CharT, class Traits, class...Xs >
    void pretty_print_between( default_tag, std::basic_ostream<CharT, Traits>&, std::basic_string<CharT, Xs...> const& ) {}

    // map magic. When iterating over the contents of a map, use the map_magic_tag:
    template<class...Xs>
    map_magic_tag<default_tag> pretty_print_descend( default_tag, std::map<Xs...> const& ) {
      return {};
    }
    template<class old_tag, class C>
    old_tag pretty_print_descend( map_magic_tag<old_tag>, C const& ) {
      return {};
    }

    // When printing a pair immediately within a map, use -> as a separator:
    template<class old_tag, class CharT, class Traits, class...Xs >
    void pretty_print_between( map_magic_tag<old_tag>, std::basic_ostream<CharT, Traits>& s, std::pair<Xs...> const& ) {
      s << CharT('-') << CharT('>');
    }
  }

  // default behavior:
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_before( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT('[');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_after( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(']');
  }
  template<class CharT, class Traits, class Tag, class Container >
  void pretty_print_between( Tag const&, std::basic_ostream<CharT, Traits>& s, Container const& ) {
    s << CharT(',');
  }
  template<class Tag, class Container>
  Tag&& pretty_print_descend( Tag&& tag, Container const& ) {
    return std::forward<Tag>(tag);
  }

  // print things by default by using <<:
  template<class Tag=decorator::default_tag, class Scalar, class CharT, class Traits>
  std::enable_if_t<!is_visitable<Scalar>{}> print( std::basic_ostream<CharT, Traits>& os, Scalar&& scalar, Tag&&=Tag{} ) {
    os << std::forward<Scalar>(scalar);
  }
  // for anything visitable (see above), use the pretty print algorithm:
  template<class Tag=decorator::default_tag, class C, class CharT, class Traits>
  std::enable_if_t<is_visitable<C>{}> print( std::basic_ostream<CharT, Traits>& os, C&& c, Tag&& tag=Tag{} ) {
    pretty_print_before( std::forward<Tag>(tag), os, std::forward<C>(c) );
    visit_first( c, [&](auto&& elem) {
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    visit_all_but_first( c, [&](auto&& elem) {
      pretty_print_between( std::forward<Tag>(tag), os, std::forward<C>(c) );
      print( os, std::forward<decltype(elem)>(elem), pretty_print_descend( std::forward<Tag>(tag), std::forward<C>(c) ) );
    });
    pretty_print_after( std::forward<Tag>(tag), os, std::forward<C>(c) );
  }
}

Код теста:

int main() {
  std::vector<int> x = {1,2,3};

  pretty_print::print( std::cout, x );
  std::cout << "\n";

  std::map< std::string, int > m;
  m["hello"] = 3;
  m["world"] = 42;

  pretty_print::print( std::cout, m );
  std::cout << "\n";
}

живой пример

При этом используются функции C ++ 14 (некоторые _t псевдонимы и auto&& лямбда-выражения), но ни одна из них не является существенной.

person Yakk - Adam Nevraumont    schedule 25.11.2014
comment
@KerrekSB рабочая версия, с некоторыми изменениями. Основная часть кода - это общие кортежи / итерации посещений и необычное форматирование (включая -> в пределах pair из maps) на этом этапе. Ядро красивой библиотеки для печати красивое и маленькое, что приятно. Я пытался сделать его легко расширяемым, но не был уверен, что мне это удалось. - person Yakk - Adam Nevraumont; 25.11.2014

Мое решение - simple.h, которое является частью scc. Все стандартные контейнеры, карты, наборы, c-массивы можно распечатать.

person Community    schedule 19.06.2011
comment
Интересно. Мне нравится подход «шаблон шаблона» для контейнеров, но работает ли он для пользовательских контейнеров и контейнеров STL с нестандартными предикатами или распределителями? (Я сделал нечто подобное для попытки реализовать bimap на C ++ 0x с использованием вариативных шаблонов.) Кроме того, похоже, что вы не используете итераторы в общих чертах для своих процедур печати; почему явное использование счетчика i? - person Kerrek SB; 19.06.2011
comment
Что такое контейнер с нестандартными предикатами? Пользовательский контейнер, соответствующий подписи, будет напечатан. Нестандартные распределители памяти сейчас не поддерживаются, но это легко исправить. Мне это просто пока не нужно. - person Leonid Volnitsky; 26.06.2011
comment
Нет веских причин использовать индекс вместо итераторов. Исторические причины. Исправлю, когда будет время. - person Leonid Volnitsky; 26.06.2011
comment
Под контейнером с нестандартными предикатами я подразумеваю что-то вроде std::set с настраиваемым компаратором или unordered_map с настраиваемым равенством. Было бы очень важно поддержать эти конструкции. - person Kerrek SB; 26.06.2011

Выйдя из одного из первых BoostCon (теперь называемого CppCon), я и двое других работали над библиотекой, чтобы сделать именно это. Основным камнем преткновения было продление namespace std. Это оказалось непозволительно для библиотеки ускорения.

К сожалению, ссылки на код больше не работают, но вы можете найти некоторые интересные моменты в обсуждениях (по крайней мере, те, которые не говорят о том, как его назвать!)

http://boost.2283326.n4.nabble.com/explore-Library-Proposal-Container-Streaming-td2619544.html

person Jeffrey Faust    schedule 16.06.2019

Вот моя версия реализации, выполненная в 2016 году.

Все в одном заголовке, поэтому использовать https://github.com/skident/eos/blob/master/include/eos/io/print.hpp

/*! \file       print.hpp
 *  \brief      Useful functions for work with STL containers. 
 *          
 *  Now it supports generic print for STL containers like: [elem1, elem2, elem3]
 *  Supported STL conrainers: vector, deque, list, set multiset, unordered_set,
 *  map, multimap, unordered_map, array
 *
 *  \author     Skident
 *  \date       02.09.2016
 *  \copyright  Skident Inc.
 */

#pragma once

// check is the C++11 or greater available (special hack for MSVC)
#if (defined(_MSC_VER) && __cplusplus >= 199711L) || __cplusplus >= 201103L
    #define MODERN_CPP_AVAILABLE 1
#endif


#include <iostream>
#include <sstream>
#include <vector>
#include <deque>
#include <set>
#include <list>
#include <map>
#include <cctype>

#ifdef MODERN_CPP_AVAILABLE
    #include <array>
    #include <unordered_set>
    #include <unordered_map>
    #include <forward_list>
#endif


#define dump(value) std::cout << (#value) << ": " << (value) << std::endl

#define BUILD_CONTENT                                                       \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss << *it << elem_separator;                                    \
        }                                                                   \


#define BUILD_MAP_CONTENT                                                   \
        std::stringstream ss;                                               \
        for (; it != collection.end(); ++it)                                \
        {                                                                   \
            ss  << it->first                                                \
                << keyval_separator                                         \
                << it->second                                               \
                << elem_separator;                                          \
        }                                                                   \


#define COMPILE_CONTENT                                                     \
        std::string data = ss.str();                                        \
        if (!data.empty() && !elem_separator.empty())                       \
            data = data.substr(0, data.rfind(elem_separator));              \
        std::string result = first_bracket + data + last_bracket;           \
        os << result;                                                       \
        if (needEndl)                                                       \
            os << std::endl;                                                \



////
///
///
/// Template definitions
///
///

//generic template for classes: deque, list, forward_list, vector
#define VECTOR_AND_CO_TEMPLATE                                          \
    template<                                                           \
        template<class T,                                               \
                 class Alloc = std::allocator<T> >                      \
        class Container, class Type, class Alloc>                       \

#define SET_TEMPLATE                                                    \
    template<                                                           \
        template<class T,                                               \
                 class Compare = std::less<T>,                          \
                 class Alloc = std::allocator<T> >                      \
            class Container, class T, class Compare, class Alloc>       \

#define USET_TEMPLATE                                                   \
    template<                                                           \
template < class Key,                                                   \
           class Hash = std::hash<Key>,                                 \
           class Pred = std::equal_to<Key>,                             \
           class Alloc = std::allocator<Key>                            \
           >                                                            \
    class Container, class Key, class Hash, class Pred, class Alloc     \
    >                                                                   \


#define MAP_TEMPLATE                                                    \
    template<                                                           \
        template<class Key,                                             \
                class T,                                                \
                class Compare = std::less<Key>,                         \
                class Alloc = std::allocator<std::pair<const Key,T> >   \
                >                                                       \
        class Container, class Key,                                     \
        class Value/*, class Compare, class Alloc*/>                    \


#define UMAP_TEMPLATE                                                   \
    template<                                                           \
        template<class Key,                                             \
                   class T,                                             \
                   class Hash = std::hash<Key>,                         \
                   class Pred = std::equal_to<Key>,                     \
                   class Alloc = std::allocator<std::pair<const Key,T> >\
                 >                                                      \
        class Container, class Key, class Value,                        \
        class Hash, class Pred, class Alloc                             \
                >                                                       \


#define ARRAY_TEMPLATE                                                  \
    template<                                                           \
        template<class T, std::size_t N>                                \
        class Array, class Type, std::size_t Size>                      \



namespace eos
{
    static const std::string default_elem_separator     = ", ";
    static const std::string default_keyval_separator   = " => ";
    static const std::string default_first_bracket      = "[";
    static const std::string default_last_bracket       = "]";


    //! Prints template Container<T> as in Python
    //! Supported containers: vector, deque, list, set, unordered_set(C++11), forward_list(C++11)
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    template<class Container>
    void print( const Container& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections with one template argument and allocator as in Python.
    //! Supported standard collections: vector, deque, list, forward_list
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    VECTOR_AND_CO_TEMPLATE
    void print( const Container<Type>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Type>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:set<T, Compare, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    SET_TEMPLATE
    void print( const Container<T, Compare, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<T, Compare, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }


    //! Prints collections like std:unordered_set<Key, Hash, Pred, Alloc> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    USET_TEMPLATE
    void print( const Container<Key, Hash, Pred, Alloc>& collection
              , const std::string& elem_separator   = default_elem_separator
              , const std::string& first_bracket    = default_first_bracket
              , const std::string& last_bracket     = default_last_bracket
              , std::ostream& os = std::cout
              , bool needEndl = true
            )
    {
        typename Container<Key, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:map<T, U> as in Python
    //! supports generic objects of std: map, multimap
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    MAP_TEMPLATE
    void print(   const Container<Key, Value>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints classes like std:unordered_map as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    UMAP_TEMPLATE
    void print(   const Container<Key, Value, Hash, Pred, Alloc>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& keyval_separator = default_keyval_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
        )
    {
        typename Container<Key, Value, Hash, Pred, Alloc>::const_iterator it = collection.begin();
        BUILD_MAP_CONTENT
        COMPILE_CONTENT
    }

    //! Prints collections like std:array<T, Size> as in Python
    //! \param collection which should be printed
    //! \param elem_separator the separator which will be inserted between elements of collection
    //! \param keyval_separator separator between key and value of map. For default it is the '=>'
    //! \param first_bracket data before collection's elements (usual it is the parenthesis, square or curly bracker '(', '[', '{')
    //! \param last_bracket data after collection's elements (usual it is the parenthesis, square or curly bracker ')', ']', '}')
    ARRAY_TEMPLATE
    void print(   const Array<Type, Size>& collection
                , const std::string& elem_separator   = default_elem_separator
                , const std::string& first_bracket    = default_first_bracket
                , const std::string& last_bracket     = default_last_bracket
                , std::ostream& os = std::cout
                , bool needEndl = true
            )
    {
        typename Array<Type, Size>::const_iterator it = collection.begin();
        BUILD_CONTENT
        COMPILE_CONTENT
    }

    //! Removes all whitespaces before data in string.
    //! \param str string with data
    //! \return string without whitespaces in left part
    std::string ltrim(const std::string& str);

    //! Removes all whitespaces after data in string
    //! \param str string with data
    //! \return string without whitespaces in right part
    std::string rtrim(const std::string& str);

    //! Removes all whitespaces before and after data in string
    //! \param str string with data
    //! \return string without whitespaces before and after data in string
    std::string trim(const std::string& str);



    ////////////////////////////////////////////////////////////
    ////////////////////////ostream logic//////////////////////
    /// Should be specified for concrete containers
    /// because of another types can be suitable
    /// for templates, for example templates break
    /// the code like this "cout << string("hello") << endl;"
    ////////////////////////////////////////////////////////////



#define PROCESS_VALUE_COLLECTION(os, collection)                            \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

#define PROCESS_KEY_VALUE_COLLECTION(os, collection)                        \
    print(  collection,                                                     \
            default_elem_separator,                                         \
            default_keyval_separator,                                       \
            default_first_bracket,                                          \
            default_last_bracket,                                           \
            os,                                                             \
            false                                                           \
    );                                                                      \

    ///< specialization for vector
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::vector<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for deque
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::deque<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for set
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for multiset
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::multiset<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

#ifdef MODERN_CPP_AVAILABLE
    ///< specialization for unordered_map
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::unordered_set<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for forward_list
    template<class T>
    std::ostream& operator<<(std::ostream& os, const std::forward_list<T>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for array
    template<class T, std::size_t N>
    std::ostream& operator<<(std::ostream& os, const std::array<T, N>& collection)
    {
        PROCESS_VALUE_COLLECTION(os, collection)
        return os;
    }
#endif

    ///< specialization for map, multimap
    MAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }

    ///< specialization for unordered_map
    UMAP_TEMPLATE
    std::ostream& operator<<(std::ostream& os, const Container<Key, Value, Hash, Pred, Alloc>& collection)
    {
        PROCESS_KEY_VALUE_COLLECTION(os, collection)
        return os;
    }
}
person Skident    schedule 24.05.2020

Если можно использовать boost, тогда вы можете использовать boost::algorithm::join. Например, чтобы распечатать вектор std::string:

#include <boost/algorithm/string/join.hpp>

std::vector<std::string> vs { "some", "string", "vector" };
std::cout << boost::algorithm::join(vs, " | ") << '\n';

Для векторов других типов вам потребуется преобразовать сначала ввести строку

#include <algorithm>
#include <iostream>
#include <numeric>
#include <vector>

#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/transformed.hpp>

int main()
{
    using boost::adaptors::transformed;
    using boost::algorithm::join;

    // Generate the vector
    std::vector<int> vi(10);
    std::iota(vi.begin(), vi.end(), -3);

    // Print out the vector
    std::cout << join(vi |
                 transformed(static_cast<std::string(*)(int)>(std::to_string)),
                 ", ")
              << '\n';
}

Де mo на Godbolt

person phuclv    schedule 06.03.2021
comment
что с вами, ребята? Никто не отправил ответ с использованием boost::algorithm::join - person phuclv; 09.06.2021

Вы можете использовать std::experimental::make_ostream_joiner:

#include <algorithm>
#include <experimental/iterator>
#include <iostream>
#include <iterator>
#include <numeric>
#include <vector>
 
int main()
{
    std::vector<int> vi(12);
    std::iota(vi.begin(), vi.end(), -5);
    std::cout << "Int vector:\n";
    std::copy(std::begin(vi),
              std::end(vi),
              std::experimental::make_ostream_joiner(std::cout, ", "));

    std::cout <<"\nString vector:\n[";
    std::vector<std::string> vs { "some", "string", "vector" };
    std::copy(std::begin(vs),
              std::end(vs),
              std::experimental::make_ostream_joiner(std::cout, "] - ["));
    std::cout << "]\n";
}

Демонстрация на Godbolt

person phuclv    schedule 19.06.2021

Для тех, кто заинтересован: я написал обобщенное решение, которое берет лучшее из обоих миров, более обобщено для любого типа диапазона и помещает в кавычки неарифметические типы (желательно для строковых типов). Кроме того, этот подход не должен иметь никаких проблем с ADL, а также избегать «сюрпризов» (поскольку он явно добавляется в индивидуальном порядке):

template <typename T>
inline constexpr bool is_string_type_v = std::is_convertible_v<const T&, std::string_view>;

template<class T>
struct range_out {
  range_out(T& range) : r_(range) {
  }
  T& r_;
  static_assert(!::is_string_type_v<T>, "strings and string-like types should use operator << directly");
};

template <typename T>
std::ostream& operator<< (std::ostream& out, range_out<T>& range) {
  constexpr bool is_string_like = is_string_type_v<T::value_type>;
  constexpr std::string_view sep{ is_string_like ? "', '" : ", " };

  if (!range.r_.empty()) {
    out << (is_string_like ? "['" : "[");
    out << *range.r_.begin();
    for (auto it = range.r_.begin() + 1; it != range.r_.end(); ++it) {
      out << sep << *it;
    }
    out << (is_string_like ? "']" : "]");
  }
  else {
    out << "[]";
  }

  return out;
}

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

std::cout << range_out{ my_vector };

Проверка в виде веревочки оставляет желать лучшего. У меня также есть static_assert проверка в моем решении, чтобы избежать std::basic_string<>, но я оставил это здесь для простоты.

person darune    schedule 21.08.2019

Для тех, кто хочет однострочник без петель:

Я не могу поверить, что никто этого не знал, но, возможно, это из-за подхода, более похожего на C. В любом случае, это совершенно безопасно сделать без цикла, в однострочном формате, ПРЕДПОЛАГАЯ, что std::vector<char> завершается нулем:

std::vector<char> test { 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\0' };
std::cout << test.data() << std::endl;

Но я бы обернул это оператором ostream, как предложил @Zorawar, на всякий случай:

template <typename T>std::ostream& operator<< (std::ostream& out, std::vector<T>& v)
{
    v.push_back('\0'); // safety-check!
    out << v.data();
    return out;
}

std::cout << test << std::endl; // will print 'Hello, world!'

Мы можем добиться аналогичного поведения, используя вместо этого printf:

fprintf(stdout, "%s\n", &test[0]); // will also print 'Hello, world!'

ЗАМЕТКА:

Перегруженный оператор ostream должен принимать вектор как неконстантный. Это может сделать программу небезопасной или ввести неправильный код. Кроме того, поскольку добавлен нулевой символ, может произойти перераспределение std::vector. Таким образом, использование циклов for с итераторами, вероятно, будет быстрее.

person ワイきんぐ    schedule 26.10.2019
comment
1. fprintf(stdout, "%s\n", &test[0]); не отличается от std::cout << test.data(), оба требуют вектора с завершающим нулем. 2. Но я бы заключил это в оператор ostream <<, который изменяет правильный операнд, - очень плохая идея. - person HolyBlackCat; 26.10.2019
comment
Я долгое время использовал fprintf(stdout, "%s\n", &test[0]); в коде, и это никогда не доставляло мне никаких проблем. Интересно! И я согласен с тем, что не так хорошо изменять вектор в операторе ostream, но мне не нравится как ручное выполнение цикла , так и с использованием итераторов. Почему-то мне кажется, что для простых операций, таких как печать std::vector<char>, стандартная библиотека должна скрывать эти вещи. Но C ++ постоянно развивается, может скоро появиться. - person ワイきんぐ; 26.10.2019

коллекция шаблонов:

применить std::cout << и std::to_string

в std::vector, std::array и std::tuple

Поскольку печать вектора в cpp оказалась на удивление большой работой (по крайней мере, по сравнению с тем, насколько проста эта задача), и, когда вы снова решаете ту же проблему, при работе с другим контейнером, здесь более общее решение ...

Содержимое коллекции шаблонов

Эта коллекция шаблонов обрабатывает 3 типа контейнеров: std::vector, std::array и std::tuple. Он определяет std::to_string() для них и позволяет напрямую распечатать их с помощью std::cout << container;.

Далее он определяет оператор ‹< для std::string << container. Благодаря этому появляется возможность компактно создавать строки, содержащие эти типы контейнеров.

Из

std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);

мы добираемся до

std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;

Код

Вы можете протестировать этот код в интерактивном режиме: здесь.

#include <iostream>
#include <string>
#include <tuple>
#include <vector>
#include <array>

namespace std
{   
    // declations: needed for std::to_string(std::vector<std::tuple<int, float>>)
    std::string to_string(std::string str);
    std::string to_string(const char *str);
    template<typename T, size_t N>
    std::string to_string(std::array<T, N> const& arr);
    template<typename T>
    std::string to_string(std::vector<T> const& vec);
    template<typename... Args>
    std::string to_string(const std::tuple<Args...>& tup);
    
    std::string to_string(std::string str)
    {
        return std::string(str);
    }
    std::string to_string(const char *str)
    {
        return std::string(str);
    }

    template<typename T, size_t N>
    std::string to_string(std::array<T, N> const& arr)
    {
        std::string s="{";
        for (std::size_t t = 0; t != N; ++t)
            s += std::to_string(arr[t]) + (t+1 < N ? ", ":"");
        return s + "}";
    }

    template<typename T>
    std::string to_string(std::vector<T> const& vec)
    {
        std::string s="[";
        for (std::size_t t = 0; t != vec.size(); ++t)
            s += std::to_string(vec[t]) + (t+1 < vec.size() ? ", ":"");
        return s + "]";
    }
    
    // to_string(tuple)
    // https://en.cppreference.com/w/cpp/utility/tuple/operator%3D
    template<class Tuple, std::size_t N>
    struct TupleString
    {
        static std::string str(const Tuple& tup)
        {
            std::string out;
            out += TupleString<Tuple, N-1>::str(tup);
            out += ", ";
            out += std::to_string(std::get<N-1>(tup));
            return out;
        }
    };
    template<class Tuple>
    struct TupleString<Tuple, 1>
    {
        static std::string str(const Tuple& tup)
        {
            std::string out;
            out += std::to_string(std::get<0>(tup));
            return out;
        }
    };
    template<typename... Args>
    std::string to_string(const std::tuple<Args...>& tup)
    {
        std::string out = "(";
        out += TupleString<decltype(tup), sizeof...(Args)>::str(tup);
        out += ")";
        return out;
    }
} // namespace std


/**
 * cout: cout << continer
 */
template <typename T, std::size_t N> // cout << array
std::ostream& operator <<(std::ostream &out, std::array<T, N> &con)
{
    out <<  std::to_string(con);
    return out;
}
template <typename T, typename A> // cout << vector
std::ostream& operator <<(std::ostream &out, std::vector<T, A> &con)
{
    out <<  std::to_string(con);
    return out;
}
template<typename... Args> // cout << tuple
std::ostream& operator <<(std::ostream &out, std::tuple<Args...> &con)
{
    out <<  std::to_string(con);
    return out;
}

/**
 * Concatenate: string << continer
 */
template <class C>
std::string operator <<(std::string str, C &con)
{
    std::string out = str;
    out += std::to_string(con);
    return out;
}
#define STR() std::string("")

int main()
{
    std::array<int, 3> arr {1, 2, 3};
    std::string sArr = std::to_string(arr);
    std::cout << "std::array" << std::endl;
    std::cout << "\ttest to_string: " << sArr << std::endl;
    std::cout << "\ttest cout <<: " << arr << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << arr) << std::endl;
    
    std::vector<std::string> vec {"a", "b"};
    std::string sVec = std::to_string(vec);
    std::cout << "std::vector" << std::endl;
    std::cout << "\ttest to_string: " << sVec << std::endl;
    std::cout << "\ttest cout <<: " << vec << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << vec) << std::endl;
    
    std::tuple<int, std::string> tup = std::make_tuple(5, "five");
    std::string sTup = std::to_string(tup);
    std::cout << "std::tuple" << std::endl;
    std::cout << "\ttest to_string: " << sTup << std::endl;
    std::cout << "\ttest cout <<: " << tup << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << tup) << std::endl;
    
    std::vector<std::tuple<int, float>> vt {std::make_tuple(1, .1), std::make_tuple(2, .2)};
    std::string sVt = std::to_string(vt);
    std::cout << "std::vector<std::tuple>" << std::endl;
    std::cout << "\ttest to_string: " << sVt << std::endl;
    std::cout << "\ttest cout <<: " << vt << std::endl;
    std::cout << "\ttest string <<: " << (std::string() << vt) << std::endl;
    
    std::cout << std::endl;
    
    std::string s1 = "s1: " + std::to_string(arr) + "; " + std::to_string(vec) + "; " + std::to_string(tup);
    std::cout << s1 << std::endl;
    
    std::string s2 = STR() << "s2: " << arr << "; " << vec << "; " << tup;
    std::cout << s2 << std::endl;

    return 0;
}

Выход

std::array
    test to_string: {1, 2, 3}
    test cout <<: {1, 2, 3}
    test string <<: {1, 2, 3}
std::vector
    test to_string: [a, b]
    test cout <<: [a, b]
    test string <<: [a, b]
std::tuple
    test to_string: (5, five)
    test cout <<: (5, five)
    test string <<: (5, five)
std::vector<std::tuple>
    test to_string: [(1, 0.100000), (2, 0.200000)]
    test cout <<: [(1, 0.100000), (2, 0.200000)]
    test string <<: [(1, 0.100000), (2, 0.200000)]

s1: {1, 2, 3}; [a, b]; (5, five)
s2: {1, 2, 3}; [a, b]; (5, five)
person Markus Dutschke    schedule 17.09.2020

person    schedule
comment
Этот ответ не дает никакой дополнительной информации по сравнению с уже существующими ответами. - person Yashas; 24.11.2017