Как использовать и получить доступ к пакету параметров шаблона из пакетов параметров

#c #c 11 #templates #variadic-templates

#c #c 11 #шаблоны #variadic-шаблоны

Вопрос:

Краткое введение

Я пытаюсь создать AddComponents() метод, который создает и добавляет несколько компонентов в объект одновременно. Я уже написал рабочий метод для добавления по одному компоненту за раз.

Он имеет следующую подпись:

 template <class TComponent, typename... TArguments>
TComponent amp; AddComponent(TArgumentsamp;amp;... arguments);
  

и используется следующим образом

 entity.AddComponent<SomeComponent>(data1, data2, data3);
  

Теперь я хотел бы создать функцию, которая добавляет несколько компонентов в один, т.Е. Принимает пакет параметров TComponents . Конечно, данные также должны быть переданы, и здесь все становится уродливым; мне в основном нужен пакет параметров пакетов параметров. Затем функция должна выполнить итерацию по TComponents (например, используя int i < sizeof...(Types) ) и вызвать AddComponent<T>(data...) для каждого TComponent . Я довольно много работал с шаблонами, но мне трудно разобраться в этом.

Чего я хочу

В идеале, я хочу, чтобы он использовался примерно так:

 entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
{ position },
{ texture, colour },
{ data1, data2 });
  

Внутренне мне нужен способ сделать что-то вроде

 for each TComponent : TComponents
{
    this->AddComponent<TComponent>(forward...(data))
}
  

Теоретически, я мог бы обойтись без этого, но, тем не менее, это кажется интересной проблемой.

Добавить компонентный код

На случай, если люди могут усомниться в том, что делает функция, вот код.

 template <class TComponent, typename... TArguments>
inline TComponent amp; Entity::AddComponent(TArgumentsamp;amp;... arguments)
{
    auto typeId = detail::GetComponentTypeID<TComponent>();

    auto component = std::make_shared<TComponent>(eventManager, *this, std::forward<TArguments>(arguments)...);

    componentList.push_back(component);
    componentDictionary.insert(std::make_pair(typeId, component));

    entityManager.AddEntityToGroup(*this, typeId);

    this->AddPolymorphism(typeId, component);
    eventManager.RaiseEvent(mbe::event::ComponentsChangedEvent(*this));

    return *component;
}
  

Комментарии:

1. Я не думаю, что это возможно именно из-за того, о чем вы просите. Может быть, что-то вроде entity.AddComponents<PositionComponent, SpriteComponent, TComponent>( std::make_tuple(position), std::make_tuple(texture, colour), std::make_tuple(data1, data2)); могло бы быть приемлемым для вас? Еще один вопрос: вы хотите, чтобы решение было строго C 11 или C 14 и C 17 были приемлемыми?

2. Все, что работает и становится ближе всего 🙂 Конечно, make tuple не нарушает условия сделки. Я работаю с C 17 atm и переключусь на C 20, когда он будет выпущен.

Ответ №1:

Я бы использовал что-то похожее на то, что в итоге сделал STL: передавал пакеты пакетов как пакет кортежей.

Возьмем, к примеру, emplace_back в паре:

 stuct Widget {
    int a;
    std::string name = {};
};

std::pair<Widget, Widget>{
    std::piecewise_construct,
    std::forward_as_tuple(12, "values"),
    std::forward_as_tuple(31)
};
  

Здесь первый параметр указывает, что вы хотите построить кусочно, поэтому каждый аргумент будет действовать как «пакет», который будет расширен до членов пары. В этом случае элементы пары были сконструированы так, как если бы:

 Widget first{12, "values"};
Widget second{31};
  

В вашем случае, кажется, вы всегда хотите создавать N компоненты. Таким образом, решение было бы:

 template<typename Component, typename Tuple>
void addComponentUnpack(Tupleamp;amp; tuple) {
    std::apply([](autoamp;amp;... args) {
        addComponent<Component>(std::forward<decltype(args)>(args)...);
    }, std::forward<Tuple>(tuple));
}

template<typename... Components, typename... Tuples>
void addComponents(Tuplesamp;amp;... tuples) {
    (addComponentUnpack<Components, Tuples>(std::forward<Tuples>(tuples)), ...);
}
  

Таким образом, для каждого Component у вас есть соответствующий Tuple в пакетах Components и Tuples . Затем каждый кортеж отправляется в функцию, которая создает отдельный компонент типа Component с использованием кортежа Tuple .

В этом примере используются функции C 17, такие как выражения свертки и std::apply , но это может быть реализовано на C 14 с немного большим количеством кода.


Однако, если вы не хотите использовать кортежи в качестве параметров, вы всегда можете получать экземпляры ваших типов напрямую:

 template<typename... Components>
void addComponents(Componentsamp;amp;... components) {
    // ...
}
  

Его использование было бы похоже:

 addComponents(
    PositionComponent{12, 43},
    CollisionComponent{},
    SpriteComponent{"texture name"}
);

// or

addComponents<PositionComponent, CollisionComponent, SpriteComponent>(
    {12, 42},
    {},
    {"texture name"}
);
  

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

Комментарии:

1. C 17 подходит 🙂

2. Тааак здорово! Вау! Просто из любопытства, есть ли какой-либо способ разрешить использование addComponents<Comp1>({data1, data2, data3}); вместо addComponents<Comp1>(std::forward_as_tuple(data1, data2, data3)); , возможно, использования std::initializer list или чего-то еще

3. Вы вроде как объясняете это в начале, но я не уверен, как это соотносится с этим случаем

4. Списки инициализаторов @AdrianKoch не совсем совместимы с пакетами параметров. Я кое-что добавлю к ответу

5. @AdrianKoch абсолютно нет. ...components , скорее всего, временные. Добавлять их — не очень хорошая идея. Вы должны переместить их во вновь выделенный общий указатель следующим образом: AddConstructedComponent(std::make_shared<TComponent>(std::forward<TComponent>(component)))

Ответ №2:

 template<class...Ts>
struct types_t:
  std::integral_constant<std::size_t, sizeof...(Ts)>
{};
template<class T0, class...Ts>
struct types_t<T0, Ts...>:
  std::integral_constant<std::size_t, 1 sizeof...(Ts)>
  

{
использование head=T0;
используя tail=types_t;
};

 template<class Componets>
auto ComponentWorker( Components ) {
  return [this](auto components, autoamp;amp;...args) {
    using TComp0 = typename decltype(components)::head;
    // add TComp0 using all of args...
    using TCompTail = typename decltype(components)::tail;
    if constexpr( TCompTail::value != 0 )
      return ComponentWorker( TCompTail{} );
    else
      return; // void
  }
}
template <class TC0, class... TCs, class... TArguments>
auto AddComponent(TArgumentsamp;amp;... arguments) {
  using components = types_t<TC0, TCs...>;
  return ComponentWorker( components )( std::forward<TArguments>(arguments)... );
}
  

синтаксис здесь такой:

 AddComponent<A,B,C>( A_args... )( B_args... )( C_args... );
  

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

Ответ №3:

Лучшее, что я могу себе представить, — это рекурсивный addComponents() , который «использует» первый не выведенный тип шаблона с первым выведенным списком кортежей аргумента и вызывает себя с оставшимся.

В C 17 вы также можете использовать std::apply() .

Прежде всего, вам нужна базовая версия addComponents() (для прерывания рекурсии)

    template <int = 0>
   void addComponents ()
    { };
  

далее рекурсивная версия (ПРАВКА: исправлена ошибка, указанная Гийомом Ракико; спасибо!)

    template <typename T0, typename ... Ts, typename A0, typename ... As>
   void addComponents (A0 amp;amp; tpl, As amp;amp; ... as)
    {
      std::apply([](auto amp;amp; ... args)
         { return addComponent<T0>(std::forward<decltype(args)>(args)...); }, 
         std::forward<A0>(tpl));

      addComponents<Ts...>(std::forward<As>(as)...);
    }
  

Не уверен в правильности идеальной пересылки с кортежами и std::apply() (извините).

Это, очевидно, требует, чтобы аргументы для одного компонента были упакованы в кортежи.

Итак, ваш вызов становится

 entity.AddComponents<PositionComponent, SpriteComponent, TComponent>(
   std::make_tuple(position),
   std::make_tuple(texture, colour),
   std::make_tuple(data1, data2));
  

— РЕДАКТИРОВАТЬ — — Теперь я вижу, что Гийом Ракико, по сути, написал то же самое решение, но в лучшем стиле C 17: используя сворачивание шаблона и избегая рекурсии. Очень приятно.

Комментарии:

1. @GuillaumeRacicot — Я знаю, но у нас есть пересылка (если я не ошибаюсь) внутри лямбды, используемой std::apply() , которые получают значения в кортеже. Я не очень хорош в пересылке, но я вижу, что вы написали такую же пересылку внутри своего лямбда-выражения, поэтому я более уверен в правильности моего кода.

2. @GuillaumeRacicot — Я идиот! Первоначально я использовал std::tuple<As...> amp;amp; в качестве первого аргумента, чтобы вывести типы в первом кортеже и использовать их для пересылки args... в лямбда-выражении; затем я использовал std::forward<decltype(args)> вместо std::forward<As> и забыл упростить первый аргумент. Спасибо, что указали на это. Исправлено (я надеюсь).