Попытка потребовать, чтобы несколько вариативных типов были конкретными типами

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

Я знаю, что могу создать обычную вариативную функцию, которая использует va_arg а также va_list такие как printf() тем не менее, я хочу полностью их избегать.

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

Вот пример псевдокода:

class TypeIn {...}
class TypeOut{...}

template<typename... T1, typename... T2>
class MyObject {
    std::array<TypeIn*> inputs_;
    std::array<TypeOut*> outputs_;
public:
    MyObject(T1&&... inputs, T2&& ... outputs) { ... }
};

Поскольку я все еще работаю над C++17 и пока у меня нет C++20 с концепциями, модулями и сопрограммами, что было бы самым чистым, надежным и эффективным способом убедиться, что T1 это TypeIn а также T2 это TypeOutобъект класса и соответственно заполнять массивы? Я мог бы использовать вектор, но как только объект будет построен, размеры входов и выходов не изменятся.

Возможный вариант использования:

using In = TypeIn;
using Out = TypeOut;
MyObject obj( In a, In b, In c, Out x, Out y);

И я бы предпочел не использовать это для синтаксиса, если это вообще возможно:

MyObject<In,In,In,Out,Out> obj( In a, In b, In c, Out X, Out y);

Так как первый чище и читабельнее.



редактировать

Поразмыслив, я подумал, может ли это сработать...

class In {...}
class Out{...}


// template<typename T1 = In, typename T2 = Out>
// T1 must == type In and T2 must == type Out
class MyObject {
private:
     std::vector<In*> inputs_;
     std::vector<Out*> outputs_;

public:
      MyObject() = deafault;

      template<typename... Inputs> // would probably use move semantics or forwarding
      void assignInputs(Inputs&& ... inputs);

      template<typename... Outputs> // would probably use move semantics or forwarding
      void assignOutputs(Inputs&& ... outputs);
};

Однако это заставит пользователя создать объект, а затем вызвать обе функции... Я пытался сделать все это при создании...

3 ответа

Судя по вашим комментариям, я думаю, вы ищете что-то вроде

template<typename T1, typename T2>
class MyObject {
    std::vector<T1> inputs_;
    std::vector<T2> outputs_;
public:
    MyObject(std::initializer_list<T1> inputs, std::initializer_list<T2> outputs)
      : inputs_(inputs), outputs_(outputs) { }
};

Для использования в качестве

MyObject obj({a, b, c}, {x, y});

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


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

Я все это пытался сделать при строительстве...

IMHO, решение, основанное на списке инициализаторов (см. Ответ грецкого ореха), предпочтительнее, также для большей ясности с точки зрения вызывающего абонента, какие аргументы являются входными значениями, а какие - выходными.

Но... просто для удовольствия... если вы действительно хотите сделать все после построения и избежать группировки аргументов (или также их смешивания)... используя std::tuple, std::tuple_cat(), делегирование конструкторов, std::apply() а также if constexpr

#include <iostream>
#include <type_traits>
#include <tuple>
#include <vector>

struct TypeIn  {};
struct TypeOut {};

struct MyObject
 {
   template <typename TargetType, typename T>
   static auto filterVal (T && t)
    {
      if constexpr ( true == std::is_same_v<TargetType, std::decay_t<T>> )
         return std::tuple<TargetType>{std::forward<T>(t)};
      else
         return std::tuple<>{};
    }

   std::vector<TypeIn> is;
   std::vector<TypeOut> os;

   template <typename ... It, typename ... Ot>
   MyObject (std::tuple<It...> && ti, std::tuple<Ot...> && to)
    : is{ std::apply([](auto && ... ts){
                        return std::vector<TypeIn>{
                           std::forward<decltype(ts)>(ts)... }; }, ti) },
      os{ std::apply([](auto && ... ts){
                        return std::vector<TypeOut>{
                           std::forward<decltype(ts)>(ts)... }; }, to) }
    { }

   template <typename ... Ts>
   MyObject (Ts && ... ts)
    : MyObject{std::tuple_cat( filterVal<TypeIn>(std::forward<Ts>(ts)) ... ),
               std::tuple_cat( filterVal<TypeOut>(std::forward<Ts>(ts)) ... )}
    { }
 };

int main ()
 {
   TypeIn   a, b, c, d;
   TypeOut  e, f;

   MyObject mo{ a, b, c, d, e, f };

   std::cout << mo.is.size() << " TypeIn vals\n"
             << mo.os.size() << " TypeOut vals\n";
 }

Повторяю: просто для развлечения.

Прочитав комментарии и предоставленный ответ, а также в связи с характером самого языка, я пришел к выводу, что, поскольку количество типов фиксировано и известно, но их количество не является, шаблоны даже не потребуются... Он может просто перейти к базовому классу с простым конструктором и перемещать семантику при использовании initialize_list.

class In{...};
class Out{...};

class MyObject {
private:
     std::vector<In> inputs_;
     std::vector<Out> outputs_;

public:
     MyObject(initializer_list<In> inputs, initializer_list<Out> outputs ) :
       inputs_( std::move(inputs) ),
       outputs_( std::move(outputs) )
     {}
};

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

Другие вопросы по тегам