to_string template pour conteneur [c++11/c++14]
Bonjour à toutes et à tous :)
Je suis en train de me faire une fonction pour convertir en chaîne de caractères les objets de mon api. J'utilise c++14. Mais si vous avez une solution en c++17 ou c++20 n'hésitez pas à la proposer car même avec ces normes je ne vois pas comment faire :p.
Du coup j'ai des nombres, des pair, des conteneurs (je ne mets pas les tuples pour le moment mais si ça intéresse quelqu'un d'itérer sur un tuple -> https://stackoverflow.com/questions/...dtuple-in-c-11)
L'idée est d'appeler to_string avec un objet ou un nombre en paramètre et d'avoir en retour leur représentation en chaîne de caractères. La représentation dans l'exemple est un exemple simple pour développer l'algorithme.
J'ai trouvé une solution pour les conteneurs mais ça m'oblige à définir un template pour chaque type de conteneur.
J'aurais bien aimé une seule fonction qui chapeaute tout les conteneurs.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
template <typename T_Container>
std::string to_string(const T_Container& value)
{
std::stringstream ss;
ss << "{" ;
for(auto it : value)
ss << to_string(*it);
ss << "}";
return ss.str();
} |
Le problème c'est qu'elle ne permet pas au compilateur de la différencier de la fonction pour les types simples:
Code:
1 2 3 4 5 6 7 8
| template <typename T>
std::string to_string(T value)
{
std::stringstream ss;
ss.precision(std::numeric_limits<T>::digits10);
ss << "T: " << value;
return ss.str();
} |
Auriez-vous une idée ?
Un grand merci.
Voici le code.
Lien vers coliru pour éditer le code:
https://coliru.stacked-crooked.com/a/c3e36f81070384ea
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
|
#include <iostream>
#include <sstream>
#include <limits>
#include <map>
template <typename T>
std::string to_string(T value)
{
std::stringstream ss;
ss.precision(std::numeric_limits<T>::digits10);
ss << "T: " << value;
return ss.str();
}
template <>
std::string to_string(long value)
{
std::stringstream ss;
ss.precision(std::numeric_limits<long>::digits10);
ss << "long: " << value;
return ss.str();
}
template <typename T1, typename T2>
std::string to_string(const std::pair<T1, T2>& value)
{
std::stringstream ss;
ss << "{" << to_string(value.first) << ", " << to_string(value.second) << "}";
return ss.str();
}
/*
template <typename T_Container>
std::string to_string(const T_Container& value)
{
std::stringstream ss;
ss << "{" ;
for(auto it : value)
ss << to_string(*it);
ss << "}";
return ss.str();
}
/*/
template <typename ... Ts>
std::string to_string(const std::map<Ts...>& value)
{
std::stringstream ss;
ss << "{" ;
for(auto it : value)
ss << to_string(it);
ss << "}";
return ss.str();
}
//*/
int main()
{
long toto_long = 1;
float toto_float = 2.3f;
std::pair<long, float> toto_pair = {toto_long, toto_float};
std::map<long, float> toto_map_less = {{toto_long, toto_float}, {toto_long*2, toto_float}};
std::map<long, float, std::greater<long>> toto_map_greater = {{toto_long, toto_float}, {toto_long*2, toto_float}};
std::cout << to_string(toto_long) << " " << to_string(toto_float) << std::endl;
std::cout << to_string(toto_pair) << std::endl;
std::cout << to_string(toto_map_less) << std::endl;
std::cout << to_string(toto_map_greater) << std::endl;
} |
Le résultat attendu:
Code:
1 2 3 4 5 6 7 8
|
long: 1 T: 2.3
{long: 1, T: 2.3}
{{long: 1, T: 2.3}{long: 2, T: 2.3}}
{{long: 2, T: 2.3}{long: 1, T: 2.3}} |
Différencier un tuple et un array avec la technique de substition SFINAE
Bonjour,
J'ai bien avancé sur le sujet. Note je suis passé à c++17 pour pouvoir utiliser les std::string_view dans l'analyseur syntaxique. Je ne peux pas pour le moment passer à c++20 (je vais devoir faire un travail sur mes dépendances).
Serialization.h
Serialization.tpp (corps des templates)
Serialization.cpp
Serialization.ut.cpp (tests unitaires)
Le code permet de transformer en chaîne de caractères les types simples et les structures standard de c++.
Code:
1 2 3 4
|
const auto value = std::pair<std::wstring, std::vector<int>>{L"toto_1", {0, 1, 2, 3}};
std::wcout << API::toString(value) << std::endl; |
Résultat:
Citation:
{"toto_1", {0, 1, 2, 3}}
Le code permet aussi d'analyser une chaîne de caractères et de la convertir vers un type compatible.
Code:
1 2 3 4 5 6 7
|
const auto str = LR"({"toto_1", {0, 1, 2, 3}})"s;
API::Serialization::Parser parser{str};
auto value_0 = parser.GetValue<std::pair<std::wstring, std::vector<int>>>();
std::wcout << value_0.first << " : " << API::toString(value_0.second) << std::endl; |
Résultat:
Citation:
toto_1 :{0, 1, 2, 3}
Actuellement je suis sur la sérialisation des tuples. Hors il est impossible de distinguer un tuple, d'un std::array ou d'une std::pair, avec le concept de SFINAE.
J'ai essayé ça mais comme prévu le compilateur ne sait pas quel template choisir.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
#define MA_SFINAE_TO_STRING_TUPLE \
typename API::string_<typename std::tuple_element<0, T_Tuple>::type, \
typename std::tuple_element<std::tuple_size_v<T_Tuple>, T_Tuple>::type>::type
#define MA_SFINAE_NODE_TUPLE \
typename API::nodeptr_<typename std::tuple_element<0, T_Tuple>::type, \
typename std::tuple_element<std::tuple_size_v<T_Tuple>, T_Tuple>::type>::type
template <typename T_Tuple>
MA_SFINAE_TO_STRING_TUPLE toString(const T_Tuple& value);
template <class T_Tuple>
T_Tuple FromNode(const MA_SFINAE_NODE_TUPLE& node);
template <class T_Tuple>
bool IsValidNode(const MA_SFINAE_NODE_TUPLE& node);
template <class T_Tuple>
API::Serialization::Errors GetErrors(const MA_SFINAE_NODE_TUPLE& node);
namespace Tuple
{
/// Permet d'utiliser std::apply et une expression pliée
template<typename... Ts>
std::wstring toString(std::tuple<Ts...> const& value);
} |
Pour le corps des templates de la sérialisation des tuples, j'ai pour le moment implémenté juste la fonction toString.
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
template<typename... Ts>
std::wstring API::Tuple::toString(std::tuple<Ts...> const& value)
{
std::wstringstream ss;
ss << g_BeginKeyWord;
std::apply
(
[&ss](Ts const&... tuple_args)
{
std::size_t n{0};
((ss << API::IsRepresentelByString<decltype(tuple_args)>() ? LR"#(")#" + API::toString(tuple_args) + LR"#(")#" : API::toString(tuple_args)
<< (++n != sizeof...(Ts) ? g_SeparatorKeyWord + L" " : L"")), ...);
}, value
);
ss << g_EndKeyWord;
return ss.str();
}
template <typename T_Tuple>
MA_SFINAE_TO_STRING_TUPLE API::toString(const T_Tuple& value)
{
return API::Tuple::toString(value);
} |
Auriez vous une idée pour différencier les tuples des autres structures en c++17? Si vous avez une solution pour le c++20 je suis aussi preneur.
Merci d'avance.
toString(tuple{"toto", 1, 2.1f}) => {"toto", 1, 2.1}
J'ai trouvé. Je pensais pas que ça serait possible d'utiliser std::enable_if_t avec la SFINAE. Je peux donc exclure ou inclure des structures. Du coup ça m'a résolu d'autres soucis.
Mais pour ce qui concerne les tuples voici comment je me suis pris:
En tête:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
#define MA_SFINAE_TO_STRING_TUPLE \
typename API::string_<std::enable_if_t<std::is_base_of<decltype(std::tie(std::declval<T_Tuple>())), T_Tuple>::value>>::type
template <typename T_Tuple>
MA_SFINAE_TO_STRING_TUPLE toString(const T_Tuple& value);
namespace Tuple
{
/// Permet d'utiliser std::apply et une expression pliée... ne peut
/// fonctionner que si API::toString est défini pour les tuples.
template<typename... Ts>
std::wstring toString(std::tuple<Ts...> const& value);
} |
Corps:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
template<typename... Ts>
std::wstring API::Tuple::toString(std::tuple<Ts...> const& value)
{
// Je n'utilise pas un stringstream car j'utilise wxWidgets et c'est définition d'opérateur << gâchent tout :p.
std::wstring str = g_BeginKeyWord;
std::apply([&str](auto&&... tuple_args)
{
std::size_t n{0};
// expression repliée
((
str += (API::IsRepresentedByString<decltype(tuple_args)>() ? LR"#(")#"s + API::toString(tuple_args) + LR"#(")#"s : API::toString(tuple_args))
+ (++n != sizeof...(Ts) ? std::wstring{g_SeparatorKeyWord + L" "s} : std::wstring{L""s})
), ...);
}, value);
str += g_EndKeyWord;
return str;
}
template <typename T_Tuple>
MA_SFINAE_TO_STRING_TUPLE API::toString(const T_Tuple& value)
{
return API::Tuple::toString(value);
} |
Test unitaire avec criterion:
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
Test(Serialization, tuple_to_string_0)
{
typedef std::tuple<std::wstring, int, float> tuple;
const auto tuple_str = LR"({"toto", 1, -2.1})"s;
const auto tuple_value = tuple{L"toto", 1, -2.1f};
const auto tuple_str_from_value = API::Tuple::toString(tuple_value);
cr_assert_eq(tuple_str, tuple_str_from_value,
"(tuple_str: %s) != (tuple_str_from_value: %s)",
API::to_string(tuple_str).c_str(),
API::to_string(tuple_str_from_value).c_str());
} |
A noté que j'ai dû spécialiser plus d'une fonction «IsRepesentedByString» pour les string . Car il faut prendre en compte lorsque la string est passée par référence constante ou non etc.
Code:
1 2 3 4 5 6
|
template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring>();
template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring&>();
template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring&&>();
template<> M_DLL_EXPORT bool IsRepresentedByString<const std::wstring&>();
template<> M_DLL_EXPORT bool IsRepresentedByString<const std::wstring&&>(); |