Pretty-print std :: tuple


89

Bu, oldukça şık ve tamamen genel bir çözüm geliştirmeyi başardığımız, oldukça baskı yapan STL kaplar hakkındaki önceki sorumun devamı niteliğindedir.


Bu sonraki adımda, çeşitli std::tuple<Args...>şablonlar kullanarak (yani bu kesinlikle C ++ 11) güzel baskılar eklemek istiyorum . İçin std::pair<S,T>ben sadece söylemek,

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

Bir demet yazdırmak için benzer yapı nedir?

Son öğede olduğumda ne zaman olduğumu keşfetmek için SFINAE'yi paketten çıkarmayı, dizinleri geçirmeyi ve SFINAE'yi kullanarak çeşitli şablon argüman yığınlarını denedim, ancak başarılı olamadım. Kırık kodumla size yük olmayacağım; sorun açıklaması umarım yeterince doğrudandır. Esasen şu davranışı istiyorum:

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

Önceki soru ile aynı genellik seviyesini (char / wchar_t, çift sınırlayıcılar) dahil etmek için bonus puanlar!


Birisi buradaki kodlardan herhangi birini bir kitaplığa koydu mu? Ya da birinin kapıp kullanabileceği bir .hpp-with-every-in-hangisi?
einpoklum

@einpoklum: Belki cxx-prettyprint ? O koda bunun için ihtiyacım vardı.
Kerrek SB

1
Harika bir soru ve "Size bozuk kodumla yük olmamalıyım" için +1, ancak akılsız "ne denediniz" ordularını savuşturmada gerçekten başarılı olmasına şaşırmama rağmen.
Don Hatch

Yanıtlar:


78

Yaşasın, endeksler ~

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'da canlı örnek.


Sınırlayıcı şeyler için şu kısmi uzmanlıkları eklemeniz yeterlidir:

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

ve değişim operator<<ve print_tuplebuna göre:

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

Ve

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: Şu anda kendimi test ediyorum ve düzeltiyorum, yine de Ideone'da garip çıktılar alıyorum.
Xeo

Bence akışları ve dizeleri de karıştırıyorsun. "Std :: cout << std :: cout" a benzer bir şey yazıyorsunuz. Başka bir deyişle, TuplePrinterbir operator<<.
Kerrek SB

1
@Thomas: Aşırı yük class Tupleiçin kullanamazsınız operator<<- her şey için seçilir. Bir tür çeşitli argümanlara ihtiyaç olduğunu ima eden bir kısıtlamaya ihtiyaç duyacaktır.
Xeo

1
@DanielFrey: budur çözülmüş bir problem, liste-başlatma garanti soldan sağa sırayla: swallow{(os << get<Is>(t))...};.
Xeo

6
@Xeo Sakıncası yoksa cppreference için kırlangıçını ödünç aldım .
Cubbi

23

C ++ 17'de bunu, Katlama ifadelerinden , özellikle tekli sol katlamadan yararlanarak biraz daha az kodla başarabiliriz :

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

Canlı Demo çıktıları:

(5, Merhaba, -0.1)

verilen

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

Açıklama

Tekli sol kıvrımımız formdadır

... op pack

burada opSenaryomuzda virgül operatörü ve packbenzeri bir genleşmemiş bağlamda, bizim tuple içeren ifadesidir:

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

Öyleyse böyle bir demetim varsa:

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

Ve std::integer_sequencedeğerleri tür olmayan bir şablonla belirtilen a (yukarıdaki koda bakın)

size_t... I

Sonra ifade

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

Genişler

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

Hangisi basacak

5Merhaba-0.1

Bu iğrençtir, bu nedenle, ilk öğe olmadığı sürece önce yazdırılacak bir virgül ayırıcısı eklemek için biraz daha hile yapmamız gerekir.

Bunu başarmak için, mevcut dizin birinci değilse packyazdırmak için katlama ifadesinin kısmını değiştiririz , dolayısıyla * kısmı :" ,"I(I == 0? "" : ", ")

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

Ve şimdi alacağız

5, Merhaba, -0.1

Hangisi daha güzel görünüyor (Not: Bu cevapla benzer çıktı istedim )

* Not: Virgülle ayırmayı benim sonuçta yaptığımdan çok çeşitli şekillerde yapabilirsiniz. Başlangıçta virgül şartlı eklenen sonra yerine önce karşı test ederek std::tuple_size<TupType>::value - 1, ama ben onun yerine karşı test yüzden çok uzundu sizeof...(I) - 1, ama sonunda ben kopyalanan Xeo ve biz bende ne var ile sona erdi.


1
if constexprTemel durum için de kullanabilirsiniz .
Kerrek SB

@KerrekSB: Virgül yazdırıp yazdırmayacağınıza karar vermek için mi? Fena fikir değil, keşke üçlü olarak gelseydi.
AndyG

Koşullu ifade zaten potansiyel bir sabit ifadedir, yani sahip olduğunuz şey zaten iyidir :-)
Kerrek SB

19

Bunu C ++ 11'de (gcc 4.7) iyi çalıştırdım. Eminim göz önünde bulundurmadığım bazı tuzaklar vardır, ancak kodun okunması kolay ve karmaşık olmadığını düşünüyorum. Garip olabilecek tek şey, son elemana ulaşıldığında sonlandırmamızı sağlayan "koruma" yapısı tuple_printer'dır. Diğer garip şey, Türler türü paketindeki türlerin sayısını döndüren boyutof ... (Türler) olabilir. Son elemanın indeksini belirlemek için kullanılır (boyut ... (Tipler) - 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
Evet, bu mantıklı görünüyor - belki de eksiksizlik için boş kayıt için başka bir uzmanlık ile.
Kerrek SB

@KerrekSB, c ++ 'da tuples basmanın basit bir yolu yok, python fonksiyonunda örtük olarak bir demet döndürür ve onları paketlemem gereken bir fonksiyondan çoklu değişkenleri döndürmek için c ++' da yazdırabilirsiniz std::make_tuple(). ancak baskı anında, main()bir sürü hata atıyor!
Anu

17

Cppreference uygulamasının burada yayınlanmamasına şaşırdım , bu yüzden gelecek nesil için yapacağım. Bulması std::tuple_catkolay olmadığı için belgede gizlidir . Buradaki diğer çözümlerden bazıları gibi bir koruma yapısı kullanıyor, ancak bence onlarınki daha basit ve takibi daha kolay.

#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

Ve bir test:

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

Çıktı:

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

Canlı Demo


4

AndyG koduna göre, C ++ 17 için

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

çıktı ile:

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

3

Bjarne Stroustrup'un C ++ Programlama Dili örneğine dayanarak , sayfa 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;
    }

Çıktı:

()
("One meatball")
(1, 1.2, "Tail!")

3

std::apply(C ++ 17) 'den yararlanarak std::index_sequencetek bir işlevi bırakabilir ve tanımlayabiliriz:

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

Ya da bir stringstream yardımıyla hafifçe süslenmiş:

#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

@Kerrek SB tarafından önerildiği gibi boş tuple için bir uzmanlık içeren @Tony Olsson'a benzer bir tane daha.

#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'nin cevabını beğendim, ancak stringstream yığın kullanıyor. Bu önlenebilir:

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

Katlama ifadelerini kullanan önceki yanıtlardan hoşlanmadığım bir şey, ilk öğeyi takip etmek için dizin dizilerini veya bayrakları kullanmalarıdır, bu da güzel temiz kat ifadelerinin faydalarının çoğunu ortadan kaldırır.

İşte indekslemeye ihtiyaç duymayan, ancak benzer bir sonuca ulaşan bir örnek. (Bazıları kadar karmaşık değil, ancak daha fazlası eklenebilir.)

Teknik, katlamanın size zaten verdiği şeyi kullanmaktır: bir öğe için özel bir durum. Yani, bir eleman katlama sadece şeklinde genişler elem[0], sonra 2 eleman olur elem[0] + elem[1], burada +bir işlem var. İstediğimiz şey, bir öğenin akışa yalnızca o öğeyi yazması ve daha fazla öğe için aynısını yapıp her birine ek bir "," yazısıyla birleştirilmesidir. Bu yüzden bunu c ++ katlama ile eşleştirerek, her elemanın akışa bir nesne yazma eylemi olmasını istiyoruz. Bizim istiyoruz +işlemi "" yazma ile iki yazar serpmek için olmak. Öyleyse, önce tuple dizimizi bir dizi yazma eylemine dönüştürün, CommaJoinerben buna çağırdım, sonra bu eylem operator+için iki eylemi istediğimiz şekilde birleştirmek için bir ekleyin , arasına "," ekleyin :

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

Godbolt'a üstünkörü bir bakış, bunun da oldukça iyi derlendiğini, tüm thunks dediği şeylerin düzleştirildiğini gösteriyor.

Bununla birlikte, boş bir tuple ile başa çıkmak için ikinci bir aşırı yüklemeye ihtiyaç duyacaktır.


0

İşte son zamanlarda bir demet yazdırmak için yaptığım bazı kodlar.

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

Örnek durumunuzu kullanarak:

auto a = std::make_tuple(5, "Hello", -0.1); 
cout << a << '\n'; // (5, Hello, -0.1)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.