Попытка потребовать, чтобы несколько вариативных типов были конкретными типами
В настоящее время это псевдокод, так как я работаю над этой идеей, прежде чем начну писать ее как полный код.
Я знаю, что могу создать обычную вариативную функцию, которая использует 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) )
{}
};
Изменить - я не совсем пытался показать код, который будет компилироваться, это было больше просто проиллюстрировать точку, однако я исправил ее, чтобы она правильно соответствовала будущим читателям.