Pretty-print std:: кортеж


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


в этом следующем шаге я хотел бы включить pretty-printing for 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, парные разделители), что и предыдущий вопрос!

7 74

7 ответов:

Ура!показатели~

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

Я получил это работает нормально в C++11 (gcc 4.7). Есть я уверен, что некоторые подводные камни я не рассматривал, но я думаю, что код легко читать и и не сложно. Единственное, что может быть странным, это структура "guard" tuple_printer, которая гарантирует, что мы завершим работу, когда будет достигнут последний элемент. Другая странная вещь может быть sizeof...(Типа), которые возвращают количество типов в типы упаковки типа. Он используется для определения индекса последнего элемента (размер...(Типы) - 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;
}

Я удивлен реализации на 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, тест, 3.14, Foo, bar, 10, тест, 3.14, 10)

Демо

в 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 и мы закончили тем, что у меня есть.

на основе пример язык программирования 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!")

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

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

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

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

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

еще один, похожий на @Tony Olsson, включая специализацию для пустого кортежа, как предложил @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 << ")";
}