Considérons la classe suivante:
template_pack<std::vector, std::list, std::deque>;
Nous supposons que les Types ...
sont de la forme template
. Ce que je voudrais, c'est:
template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});
pour mener à:
// Class definition template <template <class...> class... Templates> class template_pack { public: template <class... Types> constexpr template_pack(const Types&...) noexcept; }; // Class template argument deduction guide template <class... Types> template_pack(const Types&...) -> template_pack</* something here */>
Comment faire en sorte que cela fonctionne?
4 Réponses :
Quelque chose comme ça semble fonctionner
#include <iostream> #include <vector> #include <list> #include <deque> template<typename... TS> struct Pack; template<typename S, typename... TS> struct Pack<S, TS...> { S s; Pack<TS...> ts; static constexpr size_t size = Pack<TS...>::size + 1; constexpr Pack(S&& s, TS&&... ts) noexcept : s(s) , ts(std::forward<TS>(ts)...) {} }; template<typename S> struct Pack<S> { S s; static constexpr size_t size = 1; constexpr Pack(S&& s) noexcept : s(s) {} }; template<> struct Pack<> { static constexpr size_t size = 0; }; template<typename... TS> constexpr auto make_pack(TS&&... ts) noexcept { return Pack<TS...>(std::forward<TS>(ts)...); } int main() { auto empty_pack = make_pack(); std::cout << empty_pack.size << std::endl; // 0 auto vector_pack = make_pack(std::vector<int>{}); std::cout << vector_pack.size << std::endl; // 1 auto vector_list_deque_pack = make_pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{}); std::cout << vector_list_deque_pack.size << std::endl; // 3 }
Il existe un raccourci que vous pouvez utiliser si chaque modèle n'a qu'un seul argument:
// Class template argument deduction guide template <typename... Ts> template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;
Avec un seul argument chacun, il est facile de diviser un pack parmi tous les modèles.
Malheureusement, je ne connais aucun moyen d'avoir un pack séparé par modèle sans connaître le nombre de modèles à l'avance. Par conséquent, une couche d'indirection via un assistant semble nécessaire. De plus, les guides de déduction doivent être de la forme -> template_pack
, probablement pour éviter que le compilateur fasse trop de travail ou se heurte à des problèmes impossibles. Compte tenu de cela, la classe a besoin d'un léger ajustement:
template<template<class...> class... TTs> struct result { using type = holder<TTs...>; }; template<class T> struct type {}; template<class Prev, class Current, class... Rest> auto helper() { return []<template<class...> class... PrevTTs, template<class...> class CurrTT, class... CurrTs>(result<PrevTTs...>, type<CurrTT<CurrTs...>>) { if constexpr (sizeof...(Rest) == 0) { return result<PrevTTs..., CurrTT>{}; } else { return helper<result<PrevTTs..., CurrTT>, Rest...>(); } }(Prev{}, type<Current>{}); }
Avec ce tweak, nous pouvons faire une aide (qui pourrait probablement être simplifiée pour être un peu plus simple): p>
template <template <class...> class... Templates> class holder {}; // Class definition template<class Holder> class template_pack; template <template <class...> class... Templates> class template_pack<holder<Templates...>> { public: template <class... Types> constexpr template_pack(const Types&...) noexcept {} };
J'utilise les modèles lambdas de C ++ 20 pour séparer deux modèles de leurs packs arg en ligne au lieu d'avoir une couche d'assistance supplémentaire, mais cette couche supplémentaire est toujours possible dans les normes antérieures, juste plus laid. L'assistant prend récursivement un résultat précédent, extrait un modèle à la fois, l'ajoute au résultat et s'appelle récursivement jusqu'à ce qu'il ne reste plus d'arguments.
Avec cet assistant, il devient possible de faire guide de déduction:
template <template<class> class... Templates, class... Types> template_pack(const Templates<Types>&...) -> template_pack<Templates...>;
Vous pouvez trouver un exemple complet ici . Il pourrait également être possible d'améliorer ce code de manière quelque peu significative, mais l'idée principale est là.
Comment faire fonctionner cela?
Je ne vois pas de moyen: il y a toujours quelque chose qui ne peut être déduit.
Pas exactement ce que vous avez demandé mais le mieux que je puisse imaginer passe par un type-traits personnalisés
ttw
(pour "template-template-wrapper")#include <map> #include <set> #include <vector> #include <type_traits> template <template <typename...> class C> struct ttw { template <typename ... Ts> constexpr ttw (C<Ts...> const &) { } }; template <template <typename...> class... Templates> struct template_pack { constexpr template_pack (ttw<Templates> const & ...) { } }; template <typename ... Ts> constexpr auto make_template_pack (Ts && ... ts) { return template_pack{ttw{std::forward<Ts>(ts)}...}; } int main () { template_pack tp1 {ttw{std::vector<int>{}}, ttw{std::set<long>{}}, ttw{std::map<char, short>{}}}; auto tp2 { make_template_pack(std::vector<long>{}, std::set<int>{}, std::map<char, short>{}) }; using t0 = template_pack<std::vector, std::set, std::map>; using t1 = decltype(tp1); using t2 = decltype(tp2); static_assert( std::is_same<t0, t1>::value ); static_assert( std::is_same<t0, t2>::value ); }qui, en utilisant des guides de déduction implicites, extrait le template-template du type reçu du constructeur et utilise comme paramètre de modèle.
Vous pouvez donc écrire
template_pack
avec un constructeur qui reçoitttw
template <typename ... Ts> constexpr auto make_template_pack (Ts && ... ts) { return template_pack{ttw{std::forward<Ts>(ts)}...}; }
Je "réussis" avec des traits supplémentaires:
// Class template argument deduction guide template <class... Types> template_pack(Types&&...) -> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;
Et puis, la déduction des arguments est:
template <typename T> struct template_traits; // Variadic case template <template <class...> class C, typename ... Ts> struct template_traits<C<Ts...>> { template <typename ... Us> using template_type = C<Us...>; };
Le problème est que les alias ne sont pas vraiment identiques (gcc considère que certains alias sont identiques contrairement à clang) (j'ai ouvert une question pour ce BTW)
template_traits
n'est pas std :: vector
même si pour tout T
, A code >,
template_traits
n'est pas std :: vector
.
p >
Pas sûr que ce soit possible, car techniquement, il n'y a pas de classe telle qu'un
std :: vector, etc.
Uniquement unstd::vector
. Je peux me tromper, cependant, ou cela pourrait avoir changé dans le nouveau C ++ (je suis habitué à C ++ 11).