Есть ли чистый (er) способ смешать CRTP с вариационным наследованием? - PullRequest
1 голос
/ 20 июня 2019

Изначально я не смог найти способ сделать это , но когда я формулировал этот вопрос, на ум пришли новые условия поиска, и я наконец нашел этот ответ. Я полагаю, что этот пост может одновременно служить редиректом для любого с той же проблемой (так как поиск занял некоторое время), и я также хотел бы посмотреть, есть ли способ немного улучшить синтаксический сахар, так как этот ответ - 9 лет старые и некоторые современные функции не были доступны в то время. Код из этого ответа:

#include <utility>

template <template<class> class... Mixins>
class Host : public Mixins<Host<Mixins...>>...
{
  public:
    Host(Mixins<Host>&&... args) : Mixins<Host>(std::forward<Mixins<Host>>(args))... {}
};

template <class Host> struct Mix1 {};
template <class Host> struct Mix2 {};

int main (void)
{
  typedef Host<Mix1, Mix2> TopHost;
  delete new TopHost(Mix1<TopHost>(), Mix2<TopHost>());
}

Цель состоит в том, чтобы устранить необходимость избыточного использования TopHost в каждом миксине, так как это немного раздражает и (что более важно) может что-то испортить, если неправильный тип был случайно использован с CRTP. У меня есть подозрение, что это возможно, поскольку вызов конструктора шаблонного типа с переменными аргументами, безусловно, возможен (например, new T (args ...)).

В идеале мы могли бы использовать синтаксис, такой как:

auto mix = TopHost(Mix1(args1...), Mix2(args2...), ...);

Или даже (как-то) миксины выводятся из оператора using:

auto mix = TopHost({args1...}, {args2...}, ...);

EDIT : Итак, да, действительно, я предложил решение своей проблемы, не зная. Второй синтаксис работает. Как уже упоминалось, это не требует такого использования, поэтому есть вероятность ошибки пользователя. Решение @ Evg действительно заставляет это, и хотя оно более многословно, оно технически отвечает на вопрос, поэтому я приму этот ответ (пока).

Теперь проблема, с которой я сталкиваюсь, состоит в том, что в моем приложении миксины удалили конструкторы копий, и оба метода создают копии (конечно, так устроен исходный класс). Итак, теперь возникает вопрос: есть ли способ добиться синтаксиса без копирования? Я пытался заставить что-то подобное работать, но, похоже, не могу понять, как расширять шаблоны различных размеров:

template < typename... > struct TypeList {};
template<typename TypeList> struct Variad;
template<typename... Types> struct Variad<TypeList<Types...>> {};

template<template<class> typename ...Args> struct Mixins;
template< class TemplList, class... Lists> struct Host;

template< template<class> class ...Components,  template<class...> class T, class... Variads>
struct Host<Mixins<Components...>, T<Variads>...> : 
  public Components<Host<Mixins<Components...>, T<Variads>...>>...
{
    Host(T<Variads>&&... args) : Components<Host<Mixins<Components...>, T<Variads>...>>(std::forward<T<Variads>...>(args))... {}
};

который будет использоваться как:

int main() {
  using f1 = TypeList<int,int>;
  using f2 = TypeList<int>;
  using m1 = Mixins<Mix1, Mix2>;
  Host<m1, Variad<f1>, Variad<f2>> obj(1,2,3);
}

Не обязательно чистый, но предпочтительнее в моем случае. Я не совсем уверен, как именно работает расширение, но я основал его на своей попытке создать вложенные вариады, которые, кажется, работают правильно:


#include <iostream>
#include <typeinfo>
#include <cxxabi.h>
#define get_type(x) (abi::__cxa_demangle(typeid(x).name(), NULL, NULL, NULL))

template < typename... > struct TypeList {};
template<typename TypeList> struct Variad;
template<typename... Types> struct Variad<TypeList<Types...>> {};

template<typename ...TypeListOne> struct NestedVariad;
template<template<class...> class T, typename...Types>
struct NestedVariad<T<Types>...> { };


int main() {
  using f1 = TypeList<int,int>;
  using f2 = TypeList<int>;

  NestedVariad<Variad<f1>, Variad<f2>> obj;
  cout << get_type(obj) << endl;
}

который выводит: NestedVariad<Variad<TypeList<int, int> >, Variad<TypeList<int> > >

Но при использовании аналогичного подхода для класса mixin все три аргумента передаются каждому конструктору вместо 2 в первый и 1 во второй

1 Ответ

1 голос
/ 20 июня 2019

Как сказал Олив в комментарии, работает второй синтаксис:

template <class Host> struct Mix1 {
    Mix1(int, int) {}
};

template <class Host> struct Mix2 {
    Mix2(int) {}
};

using TopHost = Host<Mix1, Mix2>;
auto mix = TopHost({1, 1}, {2});

В качестве альтернативы, вы можете сделать это:

template<class TopHost, class Mixes, class Tuple, std::size_t... ii>
auto make_mix_impl(std::index_sequence<ii...>, Tuple&& tuple)
{
    return TopHost(std::make_from_tuple<std::tuple_element_t<ii, Mixes>>(
        std::get<ii>(std::forward<Tuple>(tuple)))...);
}

template<template<class> class... Mixes, class... Tuples>
auto make_mix(Tuples&&... tuples)
{
    static_assert(sizeof...(Mixes) == sizeof...(Tuples));
    using TopHost = Host<Mixes...>;
    return make_mix_impl<TopHost, std::tuple<Mixes<TopHost>...>>(
        std::make_index_sequence<sizeof...(Mixes)>{}, 
        std::make_tuple(std::forward<Tuples>(tuples)...));
}

auto mix = make_mix<Mix1, Mix2>(std::make_tuple(1, 1), std::make_tuple(2));
...