¿Cómo funciona este código de plantilla para obtener el tamaño de una matriz?

61

Me pregunto por qué este tipo de código puede obtener el tamaño de la matriz de prueba. No estoy familiarizado con la gramática en la plantilla. Tal vez alguien podría explicar el significado del código a continuación template<typename,size_t>. Además, también se prefiere un enlace de referencia.

#define dimof(array) (sizeof(DimofSizeHelper(array)))
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

void InitDynCalls()
{
    char test[20];
    size_t n = dimof(test);
    printf("%d", n);
}
Demonio de las sombras
fuente
¿ Leíste algo como n3337 sobre C ++ 11 ? ¡Debe ser relevante para su pregunta! ¿Consideró usar std::arrayo std::vector...
Basile Starynkevitch
@BasileStarynkevitch No he leído eso. El código aparece en una biblioteca de terceros. Solo quiero descubrir el significado.
Shadow demonio
Consulte también norvig.com/21-days.html para obtener información útil (y mire quién es el autor de esa página).
Basile Starynkevitch
2
Parece un duplicado de stackoverflow.com/questions/6106158/…
sharptooth
@BasileStarynkevitch No entiendo la relevancia de ese enlace.
Carreras de ligereza en órbita

Respuestas:

86

Esto es realmente difícil de explicar, pero lo intentaré ...

En primer lugar, dimofle indica la dimensión o el número de elementos en una matriz. (Creo que "dimensión" es la terminología preferida en los entornos de programación de Windows).

Esto es necesario porque C++y Cno le brinda una forma nativa de determinar el tamaño de una matriz.


A menudo, las personas suponen sizeof(myArray)que funcionará, pero eso realmente le dará el tamaño en la memoria, en lugar de la cantidad de elementos. ¡Cada elemento probablemente toma más de 1 byte de memoria!

A continuación, podrían intentarlo sizeof(myArray) / sizeof(myArray[0]). Esto daría el tamaño en memoria de la matriz, dividido por el tamaño del primer elemento. Está bien, y se usa ampliamente en el Ccódigo. El principal problema con esto es que parecerá funcionar si pasa un puntero en lugar de una matriz. El tamaño de un puntero en la memoria generalmente será de 4 u 8 bytes, aunque lo que señala podría ser una matriz de miles de elementos.


Entonces, lo siguiente que debe probar C++es usar plantillas para forzar algo que solo funcione para matrices, y dará un error de compilación en un puntero. Se parece a esto:

template <typename T, std::size_t N>
std::size_t ArraySize(T (&inputArray)[N])
{
    return N;
}
//...
float x[7];
cout << ArraySize(x); // prints "7"

La plantilla solo funcionará con una matriz. Deducirá el tipo (no es realmente necesario, pero tiene que estar allí para que la plantilla funcione) y el tamaño de la matriz, luego devuelve el tamaño. La forma en que se escribe la plantilla no puede funcionar con un puntero.

Por lo general, puede detenerse aquí, y esto está en la biblioteca estándar de C ++ como std::size.


Advertencia: aquí abajo se mete en territorio peludo de lenguaje-abogado.


Esto es bastante bueno, pero aún falla en un caso oscuro:

struct Placeholder {
    static float x[8];
};

template <typename T, int N>
int ArraySize (T (&)[N])
{
    return N;
}

int main()
{
    return ArraySize(Placeholder::x);
}

Tenga en cuenta que la matriz xse declara , pero no se define . Para llamar a una función (es decir ArraySize) con ella, se xdebe definir .

In function `main':
SO.cpp:(.text+0x5): undefined reference to `Placeholder::x'
collect2: error: ld returned 1 exit status

No puedes vincular esto.


El código que tiene en la pregunta es una forma de evitarlo. En lugar de llamar realmente a una función, declaramos una función que devuelve un objeto del tamaño exacto . Luego usamos el sizeoftruco en eso.

Se ve igual que llamamos a la función, pero sizeofes puramente una construcción en tiempo de compilación, por lo que la función nunca en realidad se llama.

template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];
^^^^ ^                               ^^^
// a function that returns a reference to array of N chars - the size of this array in memory will be exactly N bytes

Tenga en cuenta que en realidad no puede devolver una matriz de una función, pero puede devolver una referencia a una matriz.

Entonces DimofSizeHelper(myArray)es una expresión cuyo tipo es una matriz en N chars. La expresión en realidad no tiene que ser ejecutable, pero tiene sentido en tiempo de compilación.

Por sizeof(DimofSizeHelper(myArray))lo tanto, le dirá el tamaño en tiempo de compilación de lo que obtendría si realmente llamara a la función. Aunque en realidad no lo llamamos.

Austin Powers con los ojos cruzados


No te preocupes si ese último bloque no tiene sentido. Es un truco extraño evitar un caso extraño. Es por eso que no escribe este tipo de código usted mismo, y deja que los implementadores de la biblioteca se preocupen por este tipo de tonterías.

BoBTFish
fuente
3
@Shadowfiend También está mal. Las cosas son aún más feas que eso, porque en realidad no es una declaración de una función, es una declaración de una referencia de función ... Todavía estoy descubriendo cómo explicar eso.
BoBTFish
55
¿Por qué es una declaración de una referencia de función? El "&" antes de "DimofSizeHelper" significa que el tipo de retorno es char (&) [N], de acuerdo con la respuesta de bolov.
Sombra demonio
3
@Shadowfiend Absolutamente correcto. Estaba hablando basura porque me ataron el cerebro.
BoBTFish
La dimensión no es el número de elementos en una matriz. Es decir, puede tener 1, 2, 3 o matrices de dimensiones superiores, que podrían tener el mismo número de elementos. Por ejemplo, array1D [1000], array 2D [10] [100], array3D [10] [10] [10]. cada uno con 1000 elementos.
jamesqf
1
@jamesqf En lenguajes como C ++, una matriz multidimensional es simplemente una matriz que contiene otras matrices. Desde el punto de vista del compilador, el número de elementos en la matriz primaria a menudo no tiene relación alguna con su contenido, que pueden ser matrices secundarias o terciarias.
Phlarx
27
template <typename T, size_t N>
char(&DimofSizeHelper(T(&array)[N]))[N];

// see it like this:
//                char(&DimofSizeHelper(T(&array)[N]))[N];
// template name:       DimofSizeHelper
// param name:                             array
// param type:                          T(&     )[N])
// return type:   char(&                             )[N];

DimofSizeHelperes una función de plantilla que toma un T(&)[N]parámetro, es decir, una referencia a una matriz C de N elementos de tipo Ty devuelve char (&)[N]una referencia aka a una matriz de N caracteres. En C ++, un carácter es un byte disfrazado y sizeof(char)está garantizado 1por el estándar.

size_t n = dimof(test);
// macro expansion:
size_t n = sizeof(DimofSizeHelper(array));

nse le asigna el tamaño del tipo de retorno de DimofSizeHelper, que es sizeof(char[N])cuál es N.


Esto es un poco complicado e innecesario. La forma habitual de hacerlo era:

template <class T, size_t N>
/*constexpr*/ size_t sizeof_array(T (&)[N]) { return N; }

Desde C ++ 17 esto también es innecesario, ya que tenemos std::sizequién hace esto, pero de una manera más genérica, pudiendo obtener el tamaño de cualquier contenedor de estilo stl.


Como señaló BoBTFish, es necesario para un caso de borde.

bolov
fuente
2
Es necesario si no puede usar ODR la matriz de la que desea tomar el tamaño (se declara pero no se define). Es cierto, bastante oscuro.
BoBTFish
Gracias por explicar el tipo en la función de plantilla. Eso realmente ayuda.
Shadow demonio
3
Tenemos std::extentdesde C ++ 11 que es tiempo de compilación.
LF