Руководство по дедукции для извлечения шаблона из типов - PullRequest
3 голосов
/ 06 мая 2019

Рассмотрим следующий класс:

// 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 */>

Предположим, что Types... имеют форму template <class...> class... Templates.Что бы я хотел:

template_pack pack(std::vector<int>{}, std::list<double>{}, std::deque<char>{});

, чтобы привести к:

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

Как заставить это работать?

Ответы [ 4 ]

3 голосов
/ 06 мая 2019

Как заставить это работать?

Я не вижу пути: есть что-то, что не может быть выведено.

Не совсем то, что вы просили, но лучшее, что я могу представить, это пройти через пользовательские черты типа ttw (для "template-template-wrapper")

template <template <typename...> class C>
struct ttw
 { 
   template <typename ... Ts>
   constexpr ttw (C<Ts...> const &) 
    { }
 };

что, используя неявные руководства по выводам, извлекает шаблон-шаблон из типа, полученного из конструктора, и использует его в качестве параметра шаблона.

Таким образом, вы можете написать template_pack с помощью конструктора, который получает ttw<Templates>

template <template <typename...> class... Templates>
struct template_pack
 {
   constexpr template_pack (ttw<Templates> const & ...)
    { }
 };

, который вы можете использовать следующим образом (опять же: через неявные инструкции по выводу)

template_pack tp1 {ttw{std::vector<int>{}},
                   ttw{std::set<long>{}},
                   ttw{std::map<char, short>{}}};

Проблема в том, что необходимо явно обернуть аргументы в ttw{}, потому что, для примера, std::vector<int> можно преобразовать в ttw<std::vector>, но не ttw<std::vector>. Таким образом, передавая std::vector{} вместо ttw{std::vector{}}, мы имеем обычную задачу типа "курица / яйцо" типа, который не может быть выведен, потому что для его вывода требуется преобразование, которое требует знания типа, который мы хотим вывести.

Очевидно, что вы можете требовать явного ttw упаковывания для определенной make_template_pack() функции

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

Ниже приведен полный пример компиляции

#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 );
 }
2 голосов
/ 06 мая 2019

Я "преуспеваю" с дополнительными чертами:

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

И затем вычет аргумента:

// Class template argument deduction guide
template <class... Types>
template_pack(Types&&...)
-> template_pack<template_traits<std::decay_t<Types>>::template template_type...>;

Демо

Проблемав том, что псевдонимы на самом деле не идентичны (gcc рассматривает некоторые псевдонимы как идентичные вопреки clang) (я открыл вопрос для этого BTW)

template_traits<std::vector>::template_type не std::vector, даже если длялюбой T, A, template_traits<std::vector>::template_type<T, A> не std::vector<T, A>.

1 голос
/ 06 мая 2019

Можно использовать ярлык, если у каждого шаблона есть только один аргумент:

template <template<class> class... Templates, class... Types>
template_pack(const Templates<Types>&...) -> template_pack<Templates...>;

При наличии только одного аргумента каждый может легко разделить один пакет среди всех шаблонов.

К сожалению, я не знаю ни одного способа иметь отдельный пакет для каждого шаблона, не зная заранее количество шаблонов.Следовательно, слой косвенности через помощника кажется обязательным.Кроме того, направляющие вычеты должны иметь форму -> template_pack<something>, по-видимому, чтобы избежать того, чтобы компилятор выполнял слишком много работы или сталкивался с невозможными проблемами.Учитывая это, класс нуждается в небольшой настройке:

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

С помощью этой настройки мы можем сделать помощника (который, возможно, можно было бы упростить, чтобы быть немного более простым):

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>{});
}

Я использую шаблонные лямбды в C ++ 20, чтобы отделить два шаблона от встроенных пакетов arg вместо того, чтобы иметь дополнительный вспомогательный слой, но этот дополнительный уровень все еще возможен в более ранних стандартах, просто более уродливо.Помощник рекурсивно берет предыдущий результат, разбирает один шаблон за раз, добавляет его к результату и рекурсивно вызывает себя, пока не останется аргументов.

С этим помощником становится возможным сделать выводруководство:

// Class template argument deduction guide
template <typename... Ts>
template_pack(const Ts&...) -> template_pack<typename decltype(helper<result<>, Ts...>())::type>;

Вы можете найти полный пример здесь .Также возможно несколько улучшить этот код, но основная идея есть.

0 голосов
/ 06 мая 2019

Кажется, что-то подобное работает

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