Pretty-print std :: tuple

89

Ini adalah tindak lanjut dari pertanyaan saya sebelumnya tentang wadah STL dengan pencetakan cantik , yang untuknya kami berhasil mengembangkan solusi yang sangat elegan dan sepenuhnya umum.


Pada langkah selanjutnya, saya ingin memasukkan pencetakan cantik untuk std::tuple<Args...>, menggunakan templat variadic (jadi ini benar-benar C ++ 11). Karena std::pair<S,T>, saya hanya mengatakan

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

Apa konstruksi analog untuk mencetak tupel?

Saya telah mencoba berbagai bit argumen template yang membongkar tumpukan, meneruskan indeks dan menggunakan SFINAE untuk mengetahui kapan saya berada di elemen terakhir, tetapi tidak berhasil. Saya tidak akan membebani Anda dengan kode saya yang rusak; deskripsi masalahnya semoga cukup lurus ke depan. Pada dasarnya, saya menyukai perilaku berikut:

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

Poin bonus untuk menyertakan tingkat umum yang sama (char / wchar_t, pembatas pasangan) seperti pertanyaan sebelumnya!

Kerrek SB
sumber
Apakah seseorang telah memasukkan salah satu kode di sini ke dalam perpustakaan? Atau bahkan .hpp-with-everything-in yang bisa diambil dan digunakan?
einpoklum
@einpoklum: Mungkin cxx-prettyprint ? Untuk itulah saya membutuhkan kode itu.
Kerrek SB
1
Pertanyaan yang bagus, dan +1 untuk "Saya tidak akan membebani Anda dengan kode saya yang rusak", meskipun saya terkejut tampaknya ini benar-benar berhasil menangkis gerombolan "apa yang telah Anda coba".
Don Hatch

Jawaban:

78

Yay, indeks ~

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

Contoh langsung di Ideone.


Untuk pembatas, cukup tambahkan spesialisasi parsial ini:

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

dan ubah operator<<dan print_tuplesesuai:

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

Dan

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)...};
}
Xeo
sumber
@Kerrek: Saat ini saya sedang menguji & memperbaiki diri sendiri, saya mendapatkan hasil yang aneh di Ide.
Xeo
Saya pikir Anda juga bingung dengan aliran dan string. Anda sedang menulis sesuatu yang mirip dengan "std :: cout << std :: cout". Dengan kata lain, TuplePrintertidak memiliki operator<<.
Kerrek SB
1
@ Thomas: Anda tidak bisa hanya menggunakannya class Tupleuntuk operator<<kelebihan beban - itu akan diambil untuk semua hal. Ini akan membutuhkan batasan, yang menyiratkan perlunya beberapa jenis argumen variadic.
Xeo
1
@DanielFrey: Itu memecahkan masalah, daftar-inisialisasi jaminan kiri ke kanan agar: swallow{(os << get<Is>(t))...};.
Xeo
6
@Xeo Saya meminjam menelan Anda untuk cppreference , jika Anda tidak keberatan.
Cubbi
23

Di C ++ 17 kita dapat melakukannya dengan sedikit kode dengan memanfaatkan ekspresi Fold , terutama lipatan kiri unary:

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

Keluaran Demo Langsung :

(5, Halo, -0.1)

diberikan

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

Penjelasan

Lipatan kiri unary kita berbentuk

... op pack

opdi mana dalam skenario kami adalah operator koma, dan packmerupakan ekspresi yang berisi tupel kami dalam konteks yang tidak diperluas seperti:

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

Jadi jika saya punya tupel seperti ini:

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

Dan std::integer_sequenceyang nilainya ditentukan oleh template non-tipe (lihat kode di atas)

size_t... I

Lalu ekspresinya

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

Akan diperluas menjadi

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

Yang akan dicetak

5Halo-0.1

Itu kotor, jadi kita perlu melakukan beberapa trik lagi untuk menambahkan pemisah koma untuk dicetak terlebih dahulu kecuali itu adalah elemen pertama.

Untuk mencapai itu, kami memodifikasi packbagian ekspresi lipatan untuk dicetak " ,"jika indeks saat Iini bukan yang pertama, karenanya (I == 0? "" : ", ")bagian * :

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

Dan sekarang kita akan mendapatkannya

5, Halo, -0.1

Yang terlihat lebih bagus (Catatan: Saya ingin keluaran yang serupa dengan jawaban ini )

* Catatan: Anda dapat melakukan pemisahan koma dengan berbagai cara daripada yang saya lakukan. Saya awalnya menambahkan koma secara bersyarat setelah daripada sebelumnya dengan menguji std::tuple_size<TupType>::value - 1, tapi itu terlalu lama, jadi saya mengujinya sizeof...(I) - 1, tetapi pada akhirnya saya menyalin Xeo dan kami berakhir dengan apa yang saya dapatkan.

AndyG
sumber
1
Anda juga bisa menggunakan if constexpruntuk kasus dasar.
Kerrek SB
@KerrekSB: Untuk memutuskan apakah akan mencetak koma? Bukan ide yang buruk, berharap itu datang dalam terner.
AndyG
Ekspresi bersyarat sudah merupakan ekspresi konstanta potensial, jadi yang Anda miliki sudah bagus :-)
Kerrek SB
19

Saya mendapatkan ini berfungsi dengan baik di C ++ 11 (gcc 4.7). Saya yakin ada beberapa jebakan yang belum saya pertimbangkan tetapi menurut saya kodenya mudah dibaca dan tidak rumit. Satu-satunya hal yang mungkin aneh adalah "penjaga" struct tuple_printer yang memastikan bahwa kita menghentikan ketika elemen terakhir tercapai. Hal aneh lainnya mungkin sizeof ... (Tipe) yang mengembalikan jumlah tipe dalam Tipe tipe paket. Ini digunakan untuk menentukan indeks elemen terakhir (ukuran ... (Jenis) - 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;
}
Tony Olsson
sumber
1
Ya, itu terlihat masuk akal - mungkin dengan spesialisasi lain untuk tupel kosong, untuk kelengkapan.
Kerrek SB
@KerrekSB, Tidak ada cara sederhana untuk mencetak tupel di c ++ ?, dalam fungsi python secara implisit mengembalikan tupel dan Anda cukup mencetaknya, di c ++ untuk mengembalikan beberapa variabel dari fungsi yang saya perlukan untuk mengemasnya menggunakan std::make_tuple(). tetapi pada saat mencetaknya main(), ada banyak kesalahan !, Adakah saran tentang cara yang lebih sederhana untuk mencetak tupel?
Anu
17

Saya terkejut implementasi di cppreference belum diposting di sini, jadi saya akan melakukannya untuk anak cucu. Itu disembunyikan di dokumen untuk std::tuple_catjadi tidak mudah ditemukan. Ini menggunakan struktur penjaga seperti beberapa solusi lain di sini, tetapi saya pikir solusi mereka pada akhirnya lebih sederhana dan lebih mudah diikuti.

#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

Dan tes:

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

Keluaran:

(10, Uji, 3.14, Foo, bar, 10, Uji, 3.14, 10)

Demo Langsung

AndyG
sumber
4

Berdasarkan kode AndyG, untuk 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;
}

dengan keluaran:

deep tuple: (Hello, 0.1, (1, 2, 3, four, 5.5), Z)
pengguna5673656
sumber
3

Berdasarkan contoh pada The C ++ Programming Language By Bjarne Stroustrup, halaman 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;
    }

Keluaran:

()
("One meatball")
(1, 1.2, "Tail!")
CW Holeman II
sumber
3

Memanfaatkan std::apply(C ++ 17) kita dapat menjatuhkan std::index_sequencedan mendefinisikan satu fungsi:

#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;
}

Atau, sedikit dibumbui dengan bantuan stringstream:

#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();
}
DarioP
sumber
1

Satu lagi, mirip dengan @Tony Olsson, termasuk spesialisasi untuk tupel kosong, seperti yang disarankan oleh @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 << ")";
}
Gabriel
sumber
0

Saya suka jawaban DarioP, tetapi stringstream menggunakan heap. Ini bisa dihindari:

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;
}
pengguna2445507
sumber
0

Satu hal yang saya tidak suka tentang jawaban sebelumnya yang menggunakan ekspresi lipatan adalah bahwa mereka menggunakan urutan indeks atau bendera untuk melacak elemen pertama, yang menghilangkan banyak manfaat dari ekspresi lipatan bersih yang bagus.

Berikut adalah contoh yang tidak perlu diindeks, tetapi mendapatkan hasil yang serupa. (Tidak secanggih beberapa yang lain, tetapi lebih banyak yang bisa ditambahkan.)

Tekniknya adalah menggunakan apa yang sudah diberikan lipatan: kasing khusus untuk satu elemen. Yaitu, satu elemen lipatan hanya mengembang elem[0], lalu 2 elemen elem[0] + elem[1], di mana +beberapa operasi. Yang kami inginkan adalah satu elemen menulis hanya elemen itu ke aliran, dan untuk lebih banyak elemen, lakukan hal yang sama, tetapi gabungkan masing-masing dengan tambahan penulisan ",". Jadi memetakan ini ke lipatan c ++, kami ingin setiap elemen menjadi tindakan menulis beberapa objek ke aliran. Kami ingin +operasi kami menyelingi dua penulisan dengan "," tulis. Jadi pertama-tama ubah urutan tupel kita menjadi urutan tindakan tulis, CommaJoinersaya menyebutnya, lalu untuk tindakan itu tambahkan operator+untuk menggabungkan dua tindakan seperti yang kita inginkan, menambahkan "," di antara:

#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;
}

Sekilas pandang pada godbolt menunjukkan bahwa ini terkompilasi dengan cukup baik juga, semua panggilan thunks diratakan.

Ini akan membutuhkan kelebihan kedua untuk menangani tupel kosong.

tahsmith
sumber
0

Berikut beberapa kode yang baru-baru ini saya buat untuk mencetak tupel.

#include <iostream>
#include <tuple>

using namespace std;

template<typename... Ts>
ostream& operator<<(ostream& output, const tuple<Ts...> t) {
    output << '(';
    apply([&](auto&&... args) {
        ((cout << args << ", "), ...);
    }, t);
    output << "\b\b";
    output << ')';
    return output;
}

Menggunakan contoh kasus Anda:

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)
xKaihatsu
sumber