web-dev-qa-db-fra.com

Joli imprimé std :: tuple

Ceci fait suite à ma question précédente sur jolis conteneurs STL , pour laquelle nous avons réussi à développer une solution très élégante et entièrement générale.


Dans cette prochaine étape, je voudrais inclure une jolie impression pour std::Tuple<Args...>, en utilisant des modèles variadiques (il s'agit donc strictement de C++ 11). Pour std::pair<S,T>, Je dis simplement

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

Quelle est la construction analogue pour imprimer un tuple?

J'ai essayé divers bits de décompactage de pile d'arguments de modèle, en passant des index et en utilisant SFINAE pour découvrir quand je suis au dernier élément, mais sans succès. Je ne te chargerai pas de mon code cassé; nous espérons que la description du problème est assez simple. Essentiellement, j'aimerais le comportement suivant:

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

Points bonus pour avoir inclus le même niveau de généralité (char/wchar_t, délimiteurs de paire) que la question précédente!

78
Kerrek SB

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

exemple en direct sur Ideone.


Pour le délimiteur, ajoutez simplement ces spécialisations partielles:

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

et changez le operator<< et print_Tuple en conséquence:

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

Et

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

J'ai obtenu ce bon fonctionnement en C++ 11 (gcc 4.7). Il y a, je suis sûr, des pièges auxquels je n'ai pas pensé, mais je pense que le code est facile à lire et et pas compliqué. La seule chose qui peut être étrange est la structure "garde" Tuple_printer qui garantit que nous terminons lorsque le dernier élément est atteint. L'autre chose étrange peut être sizeof ... (Types) qui renvoient le nombre de types dans le pack de types Types. Il est utilisé pour déterminer l'indice du dernier élément (taille ... (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;
}
19
Tony Olsson

Je suis surpris que l'implémentation sur cppreference n'ait pas déjà été publiée ici, donc je le ferai pour la postérité. Il est caché dans le doc pour std::Tuple_cat donc ce n'est pas facile à trouver. Il utilise une structure de garde comme certaines des autres solutions ici, mais je pense que la leur est finalement plus simple et plus facile à suivre.

#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

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

Production:

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

Démo en direct

14
AndyG

En C++ 17, nous pouvons accomplir cela avec un peu moins de code en profitant de expressions de pli , en particulier un pli gauche unaire:

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 sorties:

(5, Bonjour, -0.1)

donné

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

Explication

Notre pli gauche unaire est de la forme

... op pack

op dans notre scénario est l'opérateur virgule, et pack est l'expression contenant notre Tuple dans un contexte non développé comme:

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

Donc, si j'ai un Tuple comme ça:

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

Et un std::integer_sequence dont les valeurs sont spécifiées par un modèle non-type (voir code ci-dessus)

size_t... I

Puis l'expression

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

Se développe en

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

Qui va imprimer

5Bonjour-0,1

Ce qui est brut, nous devons donc faire un peu plus de ruse pour ajouter un séparateur de virgules à imprimer en premier, sauf s'il s'agit du premier élément.

Pour ce faire, nous modifions la partie pack de l'expression de repli pour imprimer " ," Si l'index actuel I n'est pas le premier, d'où la partie (I == 0? "" : ", ")*:

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

Et maintenant, nous aurons

5, Bonjour, -0,1

Ce qui semble plus agréable (Remarque: je voulais une sortie similaire à cette réponse )

* Remarque: Vous pouvez effectuer la séparation par virgule de différentes manières que ce que j'ai fini par faire. J'ai initialement ajouté des virgules conditionnellement après au lieu de avant en testant contre std::Tuple_size<TupType>::value - 1, Mais c'était trop long, donc j'ai plutôt testé contre sizeof...(I) - 1, mais à la fin j'ai copié Xeo et nous nous sommes retrouvés avec ce que j'ai.

13
AndyG

Basé sur l'exemple de The C++ Programming Language By Bjarne Stroustrup, page 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;
    }

Production:

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

Basé sur le code AndyG, pour 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;
}

avec sortie:

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

Un autre, similaire à celui de @Tony Olsson, incluant une spécialisation pour le Tuple vide, comme suggéré par @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 << ")";
}
1
Gabriel
1
galaxyeye