c++ - template - Generando un miembro de clase por argumento de plantilla variadic
variadic c++ (4)
Tengo una clase de plantilla donde cada argumento de plantilla representa un tipo de valor que el cálculo interno puede manejar. Las plantillas (en lugar de la sobrecarga de funciones) son necesarias porque los valores se pasan como boost :: any y sus tipos no están claros antes del tiempo de ejecución.
Para convertir correctamente a los tipos correctos, me gustaría tener una lista de miembros para cada tipo de argumento variadic, algo como esto:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::vector<T1> m_argumentsOfType1;
std::vector<T2> m_argumentsOfType2; // ...
};
O alternativamente, me gustaría almacenar los tipos de argumentos de la plantilla en una lista, como para hacer algo de magia RTTI con ella (?). Pero tampoco me queda claro cómo guardarlos en un miembro std :: initializer_list.
¡Gracias por cualquier ayuda!
Aquí hay una implementación menos que perfectamente eficiente usando boost::variant
:
template<typename ... Ts>
using variant_vector = boost::variant< std::vector<Ts>... >;
template<typename ...Ts>
struct MyClass {
using var_vec = variant_vector<Ts...>;
std::array<var_vec, sizeof...(Ts)> vecs;
};
creamos un vector variante que puede contener uno de una lista de tipos en él. Debe utilizar boost::variant
para obtener los contenidos (lo que significa conocer el tipo de contenido o escribir un visitante).
Luego almacenamos una matriz de estos vectores variantes, uno por tipo.
Ahora, si su clase solo tiene un tipo de datos, puede eliminar la matriz y solo tener un miembro del tipo var_vec
.
No puedo ver por qué querría un vector de cada tipo. Pude ver querer un vector donde cada elemento es de cualquier tipo. Eso sería un vector<variant<Ts...>>
, a diferencia de la variant<vector<Ts>...>
anterior variant<vector<Ts>...>
.
variant<Ts...>
es el boost
unión con tipo. any
es el boost
smart void*
. optional
es el boost
hay o no.
template<class...Ts>
boost::optional<boost::variant<Ts...>> to_variant( boost::any );
puede ser una función útil, que toma un any
y trata de convertirlo a cualquiera de los tipos Ts...
en la variant
, y lo devuelve si tiene éxito (y devuelve un optional
vacío, si no).
Como ya ha sido insinuado, la mejor manera es usar una tupla:
template<typename ...AcceptedTypes> // e.g. MyClass<T1, T2>
class MyClass {
std::tuple<std::vector<AcceptedTypes>...> vectors;
};
Esta es la única forma de multiplicar los "campos" porque no puede hacer que los nombres de los campos se escriban mágicamente. Otra cosa importante puede ser obtener un acceso con nombre a ellos. Supongo que lo que estás tratando de lograr es tener múltiples vectores con tipos únicos , por lo que puedes tener la siguiente facilidad para "buscar" el vector correcto por su tipo de valor:
template <class T1, class T2>
struct SameType
{
static const bool value = false;
};
template<class T>
struct SameType<T, T>
{
static const bool value = true;
};
template <typename... Types>
class MyClass
{
public:
typedef std::tuple<vector<Types>...> vtype;
vtype vectors;
template<int N, typename T>
struct VectorOfType: SameType<T,
typename std::tuple_element<N, vtype>::type::value_type>
{ };
template <int N, class T, class Tuple,
bool Match = false> // this =false is only for clarity
struct MatchingField
{
static vector<T>& get(Tuple& tp)
{
// The "non-matching" version
return MatchingField<N+1, T, Tuple,
VectorOfType<N+1, T>::value>::get(tp);
}
};
template <int N, class T, class Tuple>
struct MatchingField<N, T, Tuple, true>
{
static vector<T>& get(Tuple& tp)
{
return std::get<N>(tp);
}
};
template <typename T>
vector<T>& access()
{
return MatchingField<0, T, vtype,
VectorOfType<0, T>::value>::get(vectors);
}
};
Aquí está el testcase para que pueda probarlo:
int main( int argc, char** argv )
{
int twelf = 12.5;
typedef reference_wrapper<int> rint;
MyClass<float, rint> mc;
vector<rint>& i = mc.access<rint>();
i.push_back(twelf);
mc.access<float>().push_back(10.5);
cout << "Test:/n";
cout << "floats: " << mc.access<float>()[0] << endl;
cout << "ints: " << mc.access<rint>()[0] << endl;
//mc.access<double>();
return 0;
}
Si usa cualquier tipo que no esté en la lista de tipos que pasó para especializar MyClass (vea este acceso comentado para el doble), obtendrá un error de compilación, no demasiado legible, pero gcc al menos señala el lugar correcto donde ha causado el problema y al menos ese mensaje de error sugiere la causa correcta del problema; aquí, por ejemplo, si intentó hacer mc.access <double> () :
error: ‘value’ is not a member of ‘MyClass<float, int>::VectorOfType<2, double>’
Una forma de hacer tal cosa, como se menciona en el comentario de πάντα-ῥεῖ es usar una tupla. Lo que no explicó (probablemente para salvarte de ti mismo) es cómo podría verse.
Aquí hay un ejemplo:
using namespace std;
// define the abomination
template<typename...Types>
struct thing
{
thing(std::vector<Types>... args)
: _x { std::move(args)... }
{}
void print()
{
do_print_vectors(std::index_sequence_for<Types...>());
}
private:
template<std::size_t... Is>
void do_print_vectors(std::index_sequence<Is...>)
{
using swallow = int[];
(void)swallow{0, (print_one(std::get<Is>(_x)), 0)...};
}
template<class Vector>
void print_one(const Vector& v)
{
copy(begin(v), end(v), ostream_iterator<typename Vector::value_type>(cout, ","));
cout << endl;
}
private:
tuple<std::vector<Types>...> _x;
};
// test it
BOOST_AUTO_TEST_CASE(play_tuples)
{
thing<int, double, string> t {
{ 1, 2, 3, },
{ 1.1, 2.2, 3.3 },
{ "one"s, "two"s, "three"s }
};
t.print();
}
Rendimiento esperado:
1,2,3,
1.1,2.2,3.3,
one,two,three,
Una solución alternativa que no usa tuplas es usar CRTP para crear una jerarquía de clases donde cada clase base es una especialización para uno de los tipos:
#include <iostream>
#include <string>
template<class L, class... R> class My_class;
template<class L>
class My_class<L>
{
public:
protected:
L get()
{
return val;
}
void set(const L new_val)
{
val = new_val;
}
private:
L val;
};
template<class L, class... R>
class My_class : public My_class<L>, public My_class<R...>
{
public:
template<class T>
T Get()
{
return this->My_class<T>::get();
}
template<class T>
void Set(const T new_val)
{
this->My_class<T>::set(new_val);
}
};
int main(int, char**)
{
My_class<int, double, std::string> c;
c.Set<int>(4);
c.Set<double>(12.5);
c.Set<std::string>("Hello World");
std::cout << "int: " << c.Get<int>() << "/n";
std::cout << "double: " << c.Get<double>() << "/n";
std::cout << "string: " << c.Get<std::string>() << std::endl;
return 0;
}