3
votes

Guide de déduction pour extraire le modèle de modèle des types

Considérons la classe suivante:

template_pack<std::vector, std::list, std::deque>;

Nous supposons que les Types ... sont de la forme template class. .. Modèles . 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?


1 commentaires

Pas sûr que ce soit possible, car techniquement, il n'y a pas de classe telle qu'un std :: vector, etc. Uniquement un std::vector . Je peux me tromper, cependant, ou cela pourrait avoir changé dans le nouveau C ++ (je suis habitué à C ++ 11).


4 Réponses :


0
votes

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
}


0 commentaires

1
votes

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à.


0 commentaires

3
votes

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çoit ttw

template <typename ... Ts>
constexpr auto make_template_pack (Ts && ... ts)
 { return template_pack{ttw{std::forward<Ts>(ts)}...}; }

0 commentaires

2
votes

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

Démo

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 :: template_type n'est pas std :: vector même si pour tout T , A code >, template_traits :: template_type n'est pas std :: vector .

p >


0 commentaires