Como esse modelo de código para obter o tamanho de uma matriz funciona?

61

Gostaria de saber por que esse tipo de código pode obter o tamanho da matriz de teste? Não estou familiarizado com a gramática no modelo. Talvez alguém possa explicar o significado do código abaixo template<typename,size_t>. Além disso, um link de referência também é preferido.

#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);
}

Demônio das Sombras
fonte
Você leu algo como n3337 sobre C ++ 11 ? Deve ser relevante para sua pergunta! Será que quis considerar o uso std::arrayou std::vector....
Basile Starynkevitch
@BasileStarynkevitch Eu não li isso. O código aparece em uma biblioteca de terceiros. Eu só quero descobrir o significado.
Shadow fiend
Consulte também norvig.com/21-days.html para obter informações úteis (e veja quem é o autor dessa página).
Basile Starynkevitch 16/10/19
2
Parece uma duplicata do stackoverflow.com/questions/6106158/… #
sharptooth
@BasileStarynkevitch Não entendo a relevância desse link.
Corridas de leveza em órbita

Respostas:

86

Isso é realmente difícil de explicar, mas vou tentar ...

Em primeiro lugar, dimofinforma a dimensão ou o número de elementos em uma matriz. (Eu acredito que "dimensão" é a terminologia preferida nos ambientes de programação do Windows).

Isso é necessário porque C++e Cnão fornece uma maneira nativa de determinar o tamanho de uma matriz.


Muitas vezes, as pessoas assumem sizeof(myArray)que funcionará, mas isso realmente fornecerá o tamanho da memória, e não o número de elementos. Cada elemento provavelmente leva mais de 1 byte de memória!

Em seguida, eles podem tentar sizeof(myArray) / sizeof(myArray[0]). Isso daria o tamanho na memória da matriz, dividido pelo tamanho do primeiro elemento. Está tudo bem, e amplamente usado em Ccódigo. O principal problema disso é que ele parecerá funcionar se você passar um ponteiro em vez de uma matriz. O tamanho de um ponteiro na memória geralmente será de 4 ou 8 bytes, mesmo que o item para o qual ele aponta possa ser uma matriz de 1000s de elementos.


Portanto, a próxima coisa a experimentar C++é usar modelos para forçar algo que só funciona para matrizes e fornecerá um erro de compilador em um ponteiro. Se parece com isso:

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

O modelo funcionará apenas com uma matriz. Deduzirá o tipo (não é realmente necessário, mas precisa estar lá para que o modelo funcione) e o tamanho da matriz e, em seguida, retornará o tamanho. A maneira como o modelo é escrito não pode funcionar com um ponteiro.

Geralmente você pode parar por aqui, e isso está no C ++ Standard Libary como std::size.


Atenção: aqui embaixo entra em território peludo-advogado de línguas.


Isso é bem legal, mas ainda falha em um caso de borda obscuro:

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

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

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

Observe que a matriz xé declarada , mas não definida . Para chamar uma função (ou seja ArraySize) com ela, xdeve ser definido .

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

Você não pode vincular isso.


O código que você tem na pergunta é uma maneira de contornar isso. Em vez de realmente chamar uma função, declaramos uma função que retorna um objeto exatamente do tamanho certo . Então nós usamos o sizeoftruque nisso.

Ele olha como nós chamamos a função, mas sizeofé puramente uma construção tempo de compilação, assim que a função nunca realmente é chamado.

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

Observe que você não pode realmente retornar uma matriz de uma função, mas pode retornar uma referência a uma matriz.

Então DimofSizeHelper(myArray)é uma expressão cujo tipo é uma matriz emN char s. A expressão não precisa ser executável, mas faz sentido em tempo de compilação.

Portanto sizeof(DimofSizeHelper(myArray)), informará o tamanho em tempo de compilação do que você obteria se realmente chamasse a função. Mesmo que na verdade não chamemos isso.

Austin Powers vesgo


Não se preocupe se esse último bloco não fizer sentido. É um truque bizarro contornar um caso estranho. É por isso que você não escreve esse tipo de código e deixa os implementadores de bibliotecas se preocupar com esse tipo de bobagem.

BoBTFish
fonte
3
@ Shadowfiend Também está errado. As coisas são ainda mais feias que isso, porque na verdade não é uma declaração de uma função, é uma declaração de uma referência de função ... Ainda estou tentando descobrir como explicar isso.
BoBTFish 16/10/19
5
Por que é uma declaração de uma referência de função? O "&" antes de "DimofSizeHelper" significa que o tipo de retorno é char (&) [N], de acordo com a resposta de bolov.
Shadow fiend
3
@ Shadowfiend Absolutamente certo. Eu estava apenas falando besteira, porque eu dei um nó no meu cérebro.
BoBTFish
Dimensão não é o número de elementos em uma matriz. Ou seja, você pode ter 1, 2, 3 ou matrizes dimensionadas mais altas, cada uma com o mesmo número de elementos. Por exemplo, array1D [1000], array 2D [10] [100], array3D [10] [10] [10]. cada um com 1000 elementos.
jamesqf
11
@jamesqf Em linguagens como C ++, uma matriz multidimensional é simplesmente uma matriz que contém outras matrizes. Do ponto de vista do compilador, o número de elementos na matriz primária geralmente não tem nenhuma relação com seu conteúdo - que podem ser matrizes secundárias ou terciárias.
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];

DimofSizeHelperé uma função de modelo que usa um T(&)[N]parâmetro - também conhecido como referência a um array C de N elementos do tipo Te retorna um char (&)[N]conhecido como referência a um array de N caracteres. Em C ++, um caractere é byte disfarçado e sizeof(char)é garantido 1pelo padrão.

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

né atribuído o tamanho do tipo de retorno de DimofSizeHelperqual é sizeof(char[N])qual é N.


Isso é um pouco complicado e desnecessário. A maneira usual de fazer isso era:

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

Desde o C ++ 17, isso também é desnecessário, pois temos o std::sizeque faz isso, mas de uma maneira mais genérica, podendo obter o tamanho de qualquer contêiner no estilo stl.


Como apontado pelo BoBTFish, é necessário um caso de borda.

Bolov
fonte
2
É necessário se você não puder usar ODR a matriz da qual deseja obter o tamanho (ela é declarada, mas não definida). É verdade que é bastante obscuro.
BoBTFish
Obrigado por ter explicado o tipo na função de modelo. Isso realmente ajuda.
Shadow fiend
3
Temos std::extentdesde C ++ 11 que é tempo de compilação.
LF