Досить друкований std :: кортеж


86

Це продовження мого попереднього запитання щодо досить друкованих контейнерів 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, роздільники пари), що і попереднє питання!


Хтось помістив будь-який код тут у бібліотеку? Або навіть .hpp - із усім - у якому можна взяти і використати?
einpoklum

@einpoklum: Може, cxx-prettyprint ? Для цього мені і знадобився цей код.
Kerrek SB

1
Чудове запитання та +1 за те, що "я не буду обтяжувати вас своїм непрацюючим кодом", хоча я здивований, що, здається, він насправді зміг відстояти безглузді натовпи "що ви пробували".
Дон Хетч,

Відповіді:


78

Так, індекси ~

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

@Kerrek: Зараз я тестую і виправляю себе, однак я отримую дивні результати на Ideone.
Xeo

Я думаю, ви також плутаєте потоки та рядки. Ви пишете щось подібне до "std :: cout << std :: cout". Іншими словами, TuplePrinterне має operator<<.
Kerrek SB

1
@Thomas: Ви не можете просто використовувати class Tupleдля operator<<перевантаження - це буде вибрано для будь-яких і всіх речей. Йому знадобиться обмеження, яке начебто передбачає необхідність якихось різнорідних аргументів.
Xeo

1
@DanielFrey: Це вирішується проблема, список-ініціалізація гарантує вліво-правильний порядок: swallow{(os << get<Is>(t))...};.
Xeo

6
@Xeo Я запозичив твою ластівку для cppreference , якщо ти не проти.
Куббі,

19

Я чудово працюю в C ++ 11 (gcc 4.7). Є, я впевнений, деякі підводні камені я не розглядав, але я думаю, що код легко читати і не складний. Єдиним, що може бути дивним, є структура "охорона" 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;
}

1
Так, це виглядає розумно - можливо, з іншою спеціалізацією для порожнього кортежу, для повноти.
Kerrek SB

@KerrekSB, Існує не простий спосіб надрукувати кортежі на c ++ ?, функція python неявно повертає кортеж, і ви можете просто роздрукувати їх на c ++, щоб повернути множинні змінні з функції, яку мені потрібно запакувати std::make_tuple(). але під час друку він main()видає купу помилок !, Будь-які пропозиції щодо більш простого способу друку кортежів?
Ану

19

У C ++ 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)>());
}

Демо виходи в реальному часі :

(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));

Який надрукує

5 Привіт-0,1

Що є валовим, тому нам потрібно зробити ще кілька хитрощів, щоб додати роздільник коми, який буде надруковано першим, якщо це не перший елемент.

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

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

А зараз отримаємо

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

Що виглядає приємніше (Примітка: я хотів отримати подібні результати, як ця відповідь )

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


1
Ви також можете використовувати if constexprдля базового випадку.
Kerrek SB

@KerrekSB: Для вирішення питання, друкувати кому? Непогана ідея, хоч би вона потрапила в трійку.
AndyG

Умовний вираз - це вже потенційний постійний вираз, тому те, що у вас є, вже добре :-)
Kerrek SB

17

Я здивований, реалізація на 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, Фу, бар, 10, Тест, 3.14, 10)

Демо в прямому ефірі


4

На основі коду 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)

3

На основі прикладу на мові програмування 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!")

3

Використовуючи std::apply(C ++ 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();
}

1

Ще одна, подібна до @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 << ")";
}

0

Мені подобається відповідь DarioP, але stringstream використовує купу. Цього можна уникнути:

template <class... Args>
std::ostream& operator<<(std::ostream& os, std::tuple<Args...> const& t) {
  os << "(";
  bool first = true;
  std::apply([&os, &first](auto&&... args) {
    auto print = [&] (auto&& val) {
      if (!first)
        os << ",";
      (os << " " << val);
      first = false;
    };
    (print(args), ...);
  }, t);
  os << " )";
  return os;
}

0

Одне, що мені не подобається у попередніх відповідях, що використовують вирази складання, це те, що вони використовують послідовності індексів або прапори для відстеження першого елемента, що позбавляє значної частини переваг приємних виразів чистої складки.

Ось приклад, який не потребує індексації, але досягає подібного результату. (Не такий витончений, як деякі інші, але можна додати більше.)

Техніка полягає у використанні того, що вам вже дає згин: спеціальний футляр для одного елемента. Тобто, складання одного елемента просто розширюється до elem[0], тоді є 2 елементи elem[0] + elem[1], де +відбувається якась операція. Ми хочемо, щоб один елемент писав саме цей елемент у потік, а для більшості елементів робив те саме, але приєднував кожного з них додатковим записом ",". Отож, зіставляючи це зі згином c ++, ми хочемо, щоб кожен елемент був дією запису якогось об’єкта в потік. Ми хочемо, щоб наша +операція проводила чергування двох записів із записом ",". Отже, спочатку перетворіть нашу послідовність кортежів у послідовність дій запису, CommaJoinerя її назвав, а потім для цієї дії додайте, operator+щоб об’єднати дві дії так, як ми хочемо, додавши "," між ними:

#include <tuple>
#include <iostream>

template <typename T>
struct CommaJoiner
{
    T thunk;
    explicit CommaJoiner(const T& t) : thunk(t) {}

    template <typename S>
    auto operator+(CommaJoiner<S> const& b) const
    {
        auto joinedThunk = [a=this->thunk, b=b.thunk] (std::ostream& os) {
            a(os);
            os << ", ";
            b(os);
        };
        return CommaJoiner<decltype(joinedThunk)>{joinedThunk};
    }

    void operator()(std::ostream& os) const
    {
        thunk(os);
    }

};

template <typename ...Ts>
std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tup)
{
    std::apply([&](auto ...ts) {
        return (... + CommaJoiner{[=](auto&os) {os << ts;}});}, tup)(os);

    return os;
}

int main() {
    auto tup = std::make_tuple(1, 2.0, "Hello");
    std::cout << tup << std::endl;
}

Побіжний погляд на болбота говорить про те, що це теж досить добре компілюється, всі дзвінки дзвінків згладжуються.

Для цього знадобиться друга перевантаження, щоб мати справу з порожнім кортежем.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.