c++ 11 programación variadic, cómo definir una torre de vectores
templates c++11 (3)
¿Cómo (si es posible) puedo usar la programación variacd de c ++ 11 para definir una serie de vector
en un cuerpo de función (o en otras palabras, una secuencia de matrices N
dimensionales con N
decrecientes hasta 0), como las variables a continuación?
vector<vector<vector<int>>> v<3>;
vector<vector<int>> v<2>;
vector<int> v<1>;
int v<0>;
Lo que imaginé es algo así como:
#include <iostream>
#include <vector>
using namespace std;
template<int ...> struct seq {};
template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};
template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };
template<int ...S>
void f(seq<S...>) {
//how do I write definitions of v<N> here?
vector<vector<...(N layers)<vector<int> ...> v<N>; //??how-to, not valid c++
vector<vector<...(N -1 layers)<vector<int> ...> v<N-1>;//??how-to, not valid c++
//...
vector<int> v<1>; //??how-to, not valid c++
int v<0>; //??how-to, not valid c++
//...
}
int main() {
f(typename gens<3>);
return 0;
}
Además, ¿sería más fácil en c ++ 14?
Gracias,
--EDITAR--
Solo para aclarar, lo que quiero decir con "una torre de vectores" se describe mejor con un N-tuplo (v_1, v_2, ..., v_N), donde N es un parámetro de plantilla integral. v_1 es de vector, v_2 es de vector>, y así sucesivamente.
--EDIT2--
Hasta ahora, las respuestas de quantdev y R han resuelto con éxito el problema de definir una N-tupla para cualquier N fijo, como 3, pero no puede generar una tupla para un N
no especificado. Además de la funcionalidad en las respuestas, necesito una función que pueda usarse como gen_tower<N>
que devuelva tuple(v1,v2,...,vN)
.
Considere el ejemplo de usar programación variadic para calcular factoriales. Necesito una función para calcular factorial factorial<N>()
para cualquier N
, además de la capacidad de escribir cualquier expresión específica <1*2*3>
manualmente. (Esa fue la razón por la que pregunté sobre la programación variadic y si c++14
lo hará más fácil).
PD
Puramente por intereses personales, quiero que esta secuencia implemente una función genérica que pueda leer una matriz N-dimensional del archivo. Todavía no sé exactamente cómo, pero creo que en el primer paso, debería ser capaz de definir el conjunto N
dimensional final, y el intermedio k
-matrices dimensionales para k
de N-1
a 1
. Puedo leer arrays bidimensionales y tridimensionales. Pero sería bueno poder leer matrices de cualquier dimensión.
No entraré en demasiados detalles para generar una sola torre ya que está explicada por otras respuestas aquí. Aquí está mi versión de gen_tower<T, I>
que genera una torre de vectores de profundidad I
Por ejemplo, gen_tower_t<int, 2>
es std::vector<std::vector<T>>
.
// Useful for defining meta-functions compactly.
template <typename T>
struct identity { using type = T; };
gen_tower <T, I>
// Forward declaration.
template <typename T, std::size_t I>
struct gen_tower;
// Convenience type alias.
template <typename T, std::size_t I>
using gen_tower_t = typename gen_tower<T, I>::type;
// Base case.
template <typename T>
struct gen_tower<T, 0> : identity<T> {};
// Wrap std::vector around tower of depth I - 1.
template <typename T, std::size_t I>
struct gen_tower : identity<std::vector<gen_tower_t<T, I - 1>>> {};
gen_towers <T, N>
Ahora podemos usar std::index_sequence
para definir N
torres.
// Generate a tuple of towers by mapping index_sequence over gen_tower.
template <typename T, std::size_t... Is>
std::tuple<gen_tower_t<T, Is>...> gen_towers_impl(std::index_sequence<Is...>);
// Make an index_sequence for N and use gen_towers_impl.
template <typename T, std::size_t N>
struct gen_towers
: identity<
decltype(gen_towers_impl<T>(std::make_index_sequence<N>()))> {};
// Convenience type aliases
template <typename T, std::size_t N>
using gen_towers_t = typename gen_towers<T, N>::type;
Ejemplos
static_assert(std::is_same<gen_tower_t<int, 0>, int>::value, "");
static_assert(std::is_same<
gen_tower_t<int, 2>,
std::vector<std::vector<int>>>::value, "");
static_assert(std::is_same<
gen_towers_t<int, 2>,
std::tuple<int, std::vector<int>>>::value, "");
static_assert(std::is_same<
gen_towers_t<int, 3>,
std::tuple<int,
std::vector<int>,
std::vector<std::vector<int>>>>::value, "");
int main() {}
No hay necesidad de variadics, un typedef
recursivo es suficiente para generar esos tipos en tiempo de compilación.
¿Cómo se implementa?
1) Proporcione una plantilla con 2 argumentos: el tipo de elementos vectoriales ( T
) y la dimensión deseada de la estructura ( size_t N
). Declare un type
typedef: se basará en la declaración de type
para la plantilla instanciada con la profundidad N-1
, de ahí la recursión.
template<typename T, size_t N>
struct VectorGenerator
{
typedef std::vector< typename VectorGenerator<T, N-1>::type > type;
};
2) Proporcione un caso de terminación para terminar la recursión, aquí una especialización de nuestra plantilla con la dimensión 0, declarando el tipo de un std::vector<T>
habitual.
template<typename T>
struct VectorGenerator<T, 0>
{
typedef std::vector<T> type;
};
Cómo usarlo ?
Ahora podemos declarar un vector v
de tipo VectorGenerator<T, N>::type
:
VectorGenerator<double, 4>::type v; // v as a depth of 4 and handle double
Pero no es muy legible o conveniente, y bastante detallado. Vamos a presentar nuevos nombres para nuestros tipos.
Este es el caso perfecto para el aliasing de plantillas , con la palabra clave (C ++ 11) para aliasing. Tenemos 2 formas diferentes de aliasing:
1) Declare un alias para una dimensión y tipo particular, aquí lo llamamos V3 para N=3
y T=double
:
using V3 = VectorGenerator<double, 3>::type; // Alias
V3 v; // Use the alias
O,
2) Declare un alias de plantilla para un tipo particular, dejando la dimensión como un parámetro de plantilla:
template <size_t N>
using V = typename VectorGenerator<double, N>::type; // Alias
V<3> v; // Use the Alias
Muestra del código final:
template<typename T, size_t N>
struct VectorGenerator
{
typedef std::vector< typename VectorGenerator<T, N-1>::type > type;
};
template<typename T>
struct VectorGenerator<T, 0>
{
typedef std::vector<T> type;
};
// Alias for V3, V2 ... usage
using V3 = VectorGenerator<double, 3>::type;
using V2 = VectorGenerator<double, 2>::type;
// Alias for V <k> usage
template <size_t N>
using V = typename VectorGenerator<double, N>::type;
int main() {
V<3> v3;
V<2> v2;
v3.push_back(v2);
return 0;
}
Notas:
- Considere la biblioteca Boost MultiDimensional Array .
- No estoy seguro de cuál es su objetivo final, pero esto podría ser una exageración.
- En cuanto a su segunda edición, declarar una
tuple
con múltiples vectores de diferentes dimensiones ahora es fácil:
Ejemplo:
auto tower = std::tuple<V<1>, V<2>, V<3>>(v1, v2, v3);
Para la generación genérica de tuplas de múltiples "torres", @mpark dio una solución C ++ 14 funcional , la adapto aquí a mi muestra de código:
template <typename T>
struct identity { using type = T; };
// Generate a tuple of towers by mapping index_sequence over gen_tower.
template <typename T, std::size_t... Is>
std::tuple<VectorGenerator<T, Is>...> gen_towers_impl(std::integer_sequence<Is...>);
// Make an index_sequence for N and use gen_towers_impl.
template <typename T, std::size_t N>
struct gen_towers
: identity<decltype(gen_towers_impl<T>(std::make_index_sequence<N>()))> {};
// Convenience type aliases
template <typename T, std::size_t N>
using gen_towers_t = typename gen_towers<T, N>::type;
Necesitará -std=c++1y
para compilarlo (e incluir los encabezados <utility>
y <tuple>
)
Vea un ejemplo de trabajo aquí .
Puede encontrar una pregunta similar pero tratando con std::map
en la sintaxis abreviada para el mapa c ++ en el mapa .
Aquí hay uno para std::vector
.
#include <iostream>
#include <vector>
template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };
template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };
int main(int argc, const char *argv[]) {
NVector<1, int>::type v1(10, 0);
NVector<2, int>::type v2;
v2.push_back(v1);
NVector<3, int>::type v3;
v3.push_back(v2);
for ( int i = 0; i < 10; ++i )
{
std::cout << v3[0][0][i] << " ";
}
std::cout << std::endl;
}
Salida:
0 0 0 0 0 0 0 0 0 0
Actualizar
Puede simplificar el uso de estos vectores con una declaración using
.
#include <iostream>
#include <vector>
template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };
template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };
template<int N, typename Val>
using V = typename NVector<N, Val>::type;
int main(int argc, const char *argv[]) {
V<1, int> v1(10, 0);
V<2, int> v2;
v2.push_back(v1);
V<3, int> v3;
v3.push_back(v2);
for ( int i = 0; i < 10; ++i )
{
std::cout << v3[0][0][i] << " ";
}
std::cout << std::endl;
}
Si desea simplificar aún más suponiendo que el tipo de valor es int
, puede usar:
#include <iostream>
#include <vector>
template<int N, typename V>
struct NVector { typedef std::vector<typename NVector<N-1, V>::type> type; };
template<typename V>
struct NVector<1, V> { typedef std::vector<V> type; };
template<int N>
using V = typename NVector<N, int>::type;
int main(int argc, const char *argv[]) {
V<1> v1(10, 0);
V<2> v2;
v2.push_back(v1);
V<3> v3;
v3.push_back(v2);
for ( int i = 0; i < 10; ++i )
{
std::cout << v3[0][0][i] << " ";
}
std::cout << std::endl;
}