Pretty-print std:: tuple

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


В этом следующем шаге я хотел бы включить красивую печать для std::tuple<Args...>, используя вариативные шаблоны (так что это строго С++ 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, пары разделителей) в качестве предыдущего вопроса!

Ответ 1

Yay, indices ~

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

Пример Live на 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)...};
}

Ответ 2

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

Ответ 3

Я удивлен, что реализация на 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, Test, 3.14, 10)

Live Demo

Ответ 4

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

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

Ответ 5

На основе примера на Язык программирования С++ By Bjarne Stroustrup, стр. 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!")

Ответ 6

Основано на коде AndyG, для С++ 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)

Ответ 8

Другой, похожий на @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 << ")";
}

Ответ 9

Используя std::apply (С++ 17), мы можем отбросить std::index_sequence и определить одну функцию:

#include <tuple>
#include <iostream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::apply([&os](auto&&... args) {((os << args << " "), ...);}, t);
  return os;
}

Или слегка украшенный с помощью струнного потока:

#include <tuple>
#include <iostream>
#include <sstream>

template<class Ch, class Tr, class... Args>
auto& operator<<(std::basic_ostream<Ch, Tr>& os, std::tuple<Args...> const& t) {
  std::basic_stringstream<Ch, Tr> ss;
  ss << "[ ";
  std::apply([&ss](auto&&... args) {((ss << args << ", "), ...);}, t);
  ss.seekp(-2, ss.cur);
  ss << " ]";
  return os << ss.str();
}