Pretty-print std :: tuple - PullRequest
       65

Pretty-print std :: tuple

78 голосов
/ 06 июня 2011

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


На следующем шаге я хотел бы включить симпатичную печать для std::tuple<Args...> с использованием шаблонов с переменными параметрами (так что это строго C ++ 11). Для std::pair<S,T> я просто говорю

std::ostream & operator<<(std::ostream & o, const std::pair<S,T> & p)
{
  return o << "(" << p.first << ", " << p.second << ")";
}

Какова аналогичная конструкция для печати кортежа?

Я пробовал разные биты распаковки стека аргументов шаблона, передачи индексов и использования SFINAE, чтобы определить, когда я нахожусь на последнем элементе, но безуспешно. Я не буду обременять тебя своим неработающим кодом; надеюсь, описание проблемы достаточно простое. По сути, я хотел бы следующее поведение:

auto a = std::make_tuple(5, "Hello", -0.1);
std::cout << a << std::endl; // prints: (5, "Hello", -0.1)

Бонусные баллы за включение того же уровня общности (char / wchar_t, пары разделителей), что и в предыдущем вопросе!

Ответы [ 8 ]

76 голосов
/ 06 июня 2011

Yay, индексы ~

namespace aux{
template<std::size_t...> struct seq{};

template<std::size_t N, std::size_t... Is>
struct gen_seq : gen_seq<N-1, N-1, Is...>{};

template<std::size_t... Is>
struct gen_seq<0, Is...> : seq<Is...>{};

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch,Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  (void)swallow{0, (void(os << (Is == 0? "" : ", ") << std::get<Is>(t)), 0)...};
}
} // aux::

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  os << "(";
  aux::print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());
  return os << ")";
}

Живой пример на Ideone.


Для разделителей просто добавьтечастичные специализации:

// Delimiters for tuple
template<class... Args>
struct delimiters<std::tuple<Args...>, char> {
  static const delimiters_values<char> values;
};

template<class... Args>
const delimiters_values<char> delimiters<std::tuple<Args...>, char>::values = { "(", ", ", ")" };

template<class... Args>
struct delimiters<std::tuple<Args...>, wchar_t> {
  static const delimiters_values<wchar_t> values;
};

template<class... Args>
const delimiters_values<wchar_t> delimiters<std::tuple<Args...>, wchar_t>::values = { L"(", L", ", L")" };

и изменения operator<< и print_tuple соответственно:

template<class Ch, class Tr, class... Args>
auto operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t)
    -> std::basic_ostream<Ch, Tr>&
{
  typedef std::tuple<Args...> tuple_t;
  if(delimiters<tuple_t, Ch>::values.prefix != 0)
    os << delimiters<tuple_t,char>::values.prefix;

  print_tuple(os, t, aux::gen_seq<sizeof...(Args)>());

  if(delimiters<tuple_t, Ch>::values.postfix != 0)
    os << delimiters<tuple_t,char>::values.postfix;

  return os;
}

И

template<class Ch, class Tr, class Tuple, std::size_t... Is>
void print_tuple(std::basic_ostream<Ch, Tr>& os, Tuple const& t, seq<Is...>){
  using swallow = int[];
  char const* delim = delimiters<Tuple, Ch>::values.delimiter;
  if(!delim) delim = "";
  (void)swallow{0, (void(os << (Is == 0? "" : delim) << std::get<Is>(t)), 0)...};
}
19 голосов
/ 04 июля 2013

У меня это работает нормально в C ++ 11 (gcc 4.7).Я уверен, что есть некоторые подводные камни, которые я не рассматривал, но я думаю, что код легко читается и не сложен.Единственное, что может быть странным, это "guard" struct tuple_printer, которая гарантирует, что мы завершаем работу, когда достигается последний элемент.Другой странной вещью может быть sizeof ... (Types), который возвращает количество типов в пакете типов Types.Используется для определения индекса последнего элемента (размер ... (Типы) - 1).

template<typename Type, unsigned N, unsigned Last>
struct tuple_printer {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value) << ", ";
        tuple_printer<Type, N + 1, Last>::print(out, value);
    }
};

template<typename Type, unsigned N>
struct tuple_printer<Type, N, N> {

    static void print(std::ostream& out, const Type& value) {
        out << std::get<N>(value);
    }

};

template<typename... Types>
std::ostream& operator<<(std::ostream& out, const std::tuple<Types...>& value) {
    out << "(";
    tuple_printer<std::tuple<Types...>, 0, sizeof...(Types) - 1>::print(out, value);
    out << ")";
    return out;
}
14 голосов
/ 29 июня 2015

Я удивлен, что реализация cppreference еще не была опубликована здесь, поэтому я сделаю это для потомков.Он скрыт в документе для std::tuple_cat, поэтому его нелегко найти.Он использует структуру защиты, как и некоторые другие решения здесь, но я думаю, что их в конечном итоге проще и понятнее.

#include <iostream>
#include <tuple>
#include <string>

// helper function to print a tuple of any size
template<class Tuple, std::size_t N>
struct TuplePrinter {
    static void print(const Tuple& t) 
    {
        TuplePrinter<Tuple, N-1>::print(t);
        std::cout << ", " << std::get<N-1>(t);
    }
};

template<class Tuple>
struct TuplePrinter<Tuple, 1> {
    static void print(const Tuple& t) 
    {
        std::cout << std::get<0>(t);
    }
};

template<class... Args>
void print(const std::tuple<Args...>& t) 
{
    std::cout << "(";
    TuplePrinter<decltype(t), sizeof...(Args)>::print(t);
    std::cout << ")\n";
}
// end helper function

И тест:

int main()
{
    std::tuple<int, std::string, float> t1(10, "Test", 3.14);
    int n = 7;
    auto t2 = std::tuple_cat(t1, std::make_pair("Foo", "bar"), t1, std::tie(n));
    n = 10;
    print(t2);
}

Вывод:

(10, Test, 3.14, Foo, bar, 10, Test, 3.14, 10)

Live Demo

13 голосов
/ 15 декабря 2016

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

template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
    std::cout << "(";
    (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
    std::cout << ")\n";
}

template<class... T>
void print (const std::tuple<T...>& _tup)
{
    print(_tup, std::make_index_sequence<sizeof...(T)>());
}

Live Demo выходы:

(5, Привет, -0,1)

данный

auto a = std::make_tuple(5, "Hello", -0.1);
print(a);

Объяснение

Наша одинарная левая складка имеет форму

... op pack

, где op в нашем сценарии - оператор запятой, а pack - выражение, содержащее наш кортеж в нерасширенном контексте, например:

(..., (std::cout << std::get<I>(myTuple))

Итак, если у меня есть такой кортеж:

auto myTuple = std::make_tuple(5, "Hello", -0.1);

И std::integer_sequence, значения которых указаны нетиповым шаблоном (см. Код выше)

size_t... I

Тогда выражение

(..., (std::cout << std::get<I>(myTuple))

Расширяется до

((std::cout << std::get<0>(myTuple)), (std::cout << std::get<1>(myTuple))), (std::cout << std::get<2>(myTuple));

Который напечатает

5Hello-0,1 * * одна тысяча сорок шесть

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

Для этого мы модифицируем часть pack выражения сгиба, чтобы вывести " ,", если текущий индекс I не первый, следовательно, часть (I == 0? "" : ", ") *:

(..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));

А теперь мы получим

5, Привет, -0,1

Что выглядит лучше (Примечание: я хотел получить такой же вывод, как этот ответ )

* Примечание. Вы можете разделять запятыми разными способами, чем я в конечном итоге. Я изначально добавил запятые условно после вместо до , протестировав против std::tuple_size<TupType>::value - 1, но это было слишком долго, поэтому я проверил вместо sizeof...(I) - 1, но в итоге я скопировал Xeo и мы получили то, что у меня есть.

3 голосов
/ 13 ноября 2015

На примере Язык программирования C ++. Бьярн Страуструп, стр. 817 :

#include <tuple>
#include <iostream>
#include <string>
#include <type_traits>
template<size_t N>
struct print_tuple{
    template<typename... T>static typename std::enable_if<(N<sizeof...(T))>::type
    print(std::ostream& os, const std::tuple<T...>& t) {
        char quote = (std::is_convertible<decltype(std::get<N>(t)), std::string>::value) ? '"' : 0;
        os << ", " << quote << std::get<N>(t) << quote;
        print_tuple<N+1>::print(os,t);
        }
    template<typename... T>static typename std::enable_if<!(N<sizeof...(T))>::type
    print(std::ostream&, const std::tuple<T...>&) {
        }
    };
std::ostream& operator<< (std::ostream& os, const std::tuple<>&) {
    return os << "()";
    }
template<typename T0, typename ...T> std::ostream&
operator<<(std::ostream& os, const std::tuple<T0, T...>& t){
    char quote = (std::is_convertible<T0, std::string>::value) ? '"' : 0;
    os << '(' << quote << std::get<0>(t) << quote;
    print_tuple<1>::print(os,t);
    return os << ')';
    }

int main(){
    std::tuple<> a;
    auto b = std::make_tuple("One meatball");
    std::tuple<int,double,std::string> c(1,1.2,"Tail!");
    std::cout << a << std::endl;
    std::cout << b << std::endl;
    std::cout << c << std::endl;
    }

Вывод:

()
("One meatball")
(1, 1.2, "Tail!")
2 голосов
/ 27 января 2019

На основе кода AndyG, для C ++ 17

#include <iostream>
#include <tuple>

template<class TupType, size_t... I>
std::ostream& tuple_print(std::ostream& os,
                          const TupType& _tup, std::index_sequence<I...>)
{
    os << "(";
    (..., (os << (I == 0 ? "" : ", ") << std::get<I>(_tup)));
    os << ")";
    return os;
}

template<class... T>
std::ostream& operator<< (std::ostream& os, const std::tuple<T...>& _tup)
{
    return tuple_print(os, _tup, std::make_index_sequence<sizeof...(T)>());
}

int main()
{
    std::cout << "deep tuple: " << std::make_tuple("Hello",
                  0.1, std::make_tuple(1,2,3,"four",5.5), 'Z')
              << std::endl;
    return 0;
}

с выводом:

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
1 голос
/ 30 декабря 2014

Еще один, похожий на @Tony Olsson's, включая специализацию для пустого кортежа, как предложено @Kerrek SB.

#include <tuple>
#include <iostream>

template<class Ch, class Tr, size_t I, typename... TS>
struct tuple_printer
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        tuple_printer<Ch, Tr, I-1, TS...>::print(out, t);
        if (I < sizeof...(TS))
            out << ",";
        out << std::get<I>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, 0, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {
        out << std::get<0>(t);
    }
};
template<class Ch, class Tr, typename... TS>
struct tuple_printer<Ch, Tr, -1, TS...>
{
    static void print(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
    {}
};
template<class Ch, class Tr, typename... TS>
std::ostream & operator<<(std::basic_ostream<Ch,Tr> & out, const std::tuple<TS...> & t)
{
    out << "(";
    tuple_printer<Ch, Tr, sizeof...(TS) - 1, TS...>::print(out, t);
    return out << ")";
}
1 голос
/ 09 августа 2013

и вот еще одна реализация:

https://github.com/galaxyeye/atlas/blob/master/atlas/io/tuple.h

с тестовым кодом:

https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/tuple.cpp

наслаждайтесь:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...