Cas les plus simples: faire correspondre les types de conteneurs
Pour le cas simple où l'entrée type correspond au type de sortie (que j'ai sinced réalisée n'est pas ce que vous demandez à propos de), remonter d'un niveau plus élevé. Au lieu de spécifier le type T
que votre conteneur utilise, et en essayant de se spécialiser sur un vector<T>
, etc., il suffit de spécifier le type du conteneur lui-même:
template <typename Container, typename Functor>
Container transform_container(const Container& c, Functor &&f)
{
Container ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
Plus de complexité: compatible avec types de valeur
Puisque vous voulez essayer de changer le type d'élément stocké par le conteneur, vous aurez besoin d'utiliser un modèle de paramètre du modèle, et de modifier l' T
que le retour de l'conteneur utilise.
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T, // <-- This is the one we'll override in the return container
typename U = std::result_of<Functor(T)>::type,
typename... Ts
>
Container<U, Ts...> transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Container<U, Ts...> ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
Ce que de l'incompatibilité des types de valeur?
Cela ne nous amène à moitié là. Il fonctionne très bien avec une transformation de l' signed
de unsigned
mais lorsque résolu avec T=int
et S=std::string
, et la manipulation d'ensembles, il tente d'instancier std::set<std::string, std::less<int>, ...>
et n'a donc pas à compiler.
Pour résoudre ce problème, nous voulons prendre un ensemble arbitraire de paramètres et de remplacer les occurrences de T
avec U
, même si elles sont les paramètres à d'autres paramètres du modèle. Ainsi, std::set<int, std::less<int>>
devrait devenir std::set<std::string, std::less<std::string>>
, et ainsi de suite. Cela implique une certaine personnalisé modèle de méta-programmation, comme suggéré par d'autres réponses.
Modèle de métaprogrammation à la rescousse
Nous allons créer un modèle, nommez - replace_type
, et de la convertir T
de U
, et K<T>
de K<U>
. Laissez-moi d'abord traiter le cas général. Si ce n'est pas basé sur un modèle de type, et il n'est pas adapté T
, son type doit demeurer K
:
template <typename K, typename ...>
struct replace_type { using type = K; };
Puis d'une spécialisation. Si ce n'est pas basé sur un modèle de type, et il ne correspond T
, son type est devenue U
:
template <typename T, typename U>
struct replace_type<T, T, U> { using type = U; };
Et enfin une étape récursive pour gérer les paramètres basés sur des modèles types. Pour chaque type basé sur un modèle du type de paramètres, de remplacer les types en conséquence:
template <template <typename... Ks> class K, typename T, typename U, typename... Ks>
struct replace_type<K<Ks...>, T, U>
{
using type = K<typename replace_type<Ks, T, U>::type ...>;
};
Et enfin la mise à jour transform_container
utilisation replace_type
:
template <
template <typename T, typename... Ts> class Container,
typename Functor,
typename T,
typename U = typename std::result_of<Functor(T)>::type,
typename... Ts,
typename Result = typename replace_type<Container<T, Ts...>, T, U>::type
>
Result transform_container(const Container<T, Ts...>& c, Functor &&f)
{
Result ret;
std::transform(std::begin(c), std::end(c), std::inserter(ret, std::end(ret)), f);
return ret;
}
Est-ce terminé?
Le problème avec cette approche est qu'il n'est pas nécessairement sans danger. Si vous êtes à la conversion de Container<MyCustomType>
de Container<SomethingElse>
, il est probable amende. Mais lors de la conversion de Container<builtin_type>
de Container<SomethingElse>
il est plausible qu'un autre paramètre de modèle ne devrait pas être converti à partir d' builtin_type
de SomethingElse
. En outre, de l'alternance des conteneurs comme std::map
ou std::array
apporter plus de problèmes à la partie.
Manutention std::map
et std::unordered_map
n'est pas trop mauvais. Le principal problème est qu' replace_type
besoin de remplacer plusieurs types. Non seulement est-il un T
-> U
de remplacement, mais également une std::pair<T, T2>
-> std::pair<U, U2>
de remplacement. Cela augmente le niveau de préoccupation pour les indésirables de type remplacements comme il n'y a plus qu'un seul type de vol. Cela dit, voici ce que j'ai trouvé pour travailler; à noter que dans les essais j'ai besoin de spécifier le type de retour de la fonction lambda qui a transformé ma carte de paires:
// map-like classes are harder. You have to replace both the key and the key-value pair types
// Give a base case replacing a pair type to resolve ambiguities introduced below
template <typename T1, typename T2, typename U1, typename U2>
struct replace_type<std::pair<T1, T2>, std::pair<T1, T2>, std::pair<U1, U2>>
{
using type = std::pair<U1, U2>;
};
// Now the extended case that replaces T1->U1 and pair<T1,T2> -> pair<T2,U2>
template <template <typename...> class K, typename T1, typename T2, typename U1, typename U2, typename... Ks>
struct replace_type<K<T1, T2, Ks...>, std::pair<const T1, T2>, std::pair<const U1, U2>>
{
using type = K<U1, U2,
typename replace_type<
typename replace_type<Ks, T1, U1>::type,
std::pair<const T1, T2>,
std::pair<const U1, U2>
>::type ...
>;
};
Qu'en est std::array?
Manutention std::array
s'ajoute à la douleur, comme son modèle les paramètres ne peuvent pas être déduites dans le modèle ci-dessus. Comme Jarod42 notes, c'est en raison de ses paramètres, y compris les valeurs, plutôt que de simplement les types. J'ai pris de cours par l'ajout de spécialisations et de l'introduction d'un helper contained_type
que des extraits T
pour moi (note de côté, par le Constructeur, c'est mieux écrit que le beaucoup plus simple typename Container::value_type
et fonctionne pour tous les types que j'ai parlé ici). Même sans l' std::array
spécialisations cela me permet de simplifier mon transform_container
modèle à la suivante (cela peut être une victoire, même sans l'appui d' std::array
):
template <typename T, size_t N, typename U>
struct replace_type<std::array<T, N>, T, U> { using type = std::array<U, N>; };
// contained_type<C>::type is T when C is vector<T, ...>, set<T, ...>, or std::array<T, N>.
// This is better written as typename C::value_type, but may be necessary for bad containers
template <typename T, typename...>
struct contained_type { };
template <template <typename ... Cs> class C, typename T, typename... Ts>
struct contained_type<C<T, Ts...>> { using type = T; };
template <typename T, size_t N>
struct contained_type<std::array<T, N>> { using type = T; };
template <
typename Container,
typename Functor,
typename T = typename contained_type<Container>::type,
typename U = typename std::result_of<Functor(T)>::type,
typename Result = typename replace_type<Container, T, U>::type
>
Result transform_container(const Container& c, Functor &&f)
{
// as above
}
Toutefois, l'implémentation actuelle de l' transform_container
utilise std::inserter
qui ne fonctionne pas avec std::array
. Alors qu'il est possible de faire plus de spécialisations, je vais laisser cela comme un modèle de la soupe de l'exercice pour un lecteur intéressé. J'ai personnellement choisi de vivre sans l'appui d' std::array
dans la plupart des cas.
Afficher le cumul des vis par exemple
La divulgation complète: bien que cette approche a été influencé par Ali citant des Kerrek SB réponse, je n'ai pas réussi à obtenir que cela fonctionne dans Visual Studio 2013, de sorte que j'ai construit au-dessus de rechange moi-même. Merci beaucoup pour les pièces de Kerrek SB réponse originale à cette question sont encore nécessaires, ainsi que pour les pousser et les encouragements de Constructeur et Jarod42.