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!
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 << ")";
}
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)...};
}
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;
}
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)
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);
Notre pli gauche unaire est de la forme
... op pack
où 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.
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!")
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)
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 << ")";
}
et voici une autre implémentation:
https://github.com/galaxyeye/atlas/blob/master/atlas/io/Tuple.h
avec le code de test:
https://github.com/galaxyeye/atlas/blob/master/libs/serialization/test/Tuple.cpp
prendre plaisir :)