Comment fonctionne ce code de modèle pour obtenir la taille d'un tableau?

61

Je me demande pourquoi ce type de code peut obtenir la taille du tableau de test? Je ne connais pas la grammaire du modèle. Peut-être que quelqu'un pourrait expliquer la signification du code ci-dessous template<typename,size_t>. En outre, un lien de référence est également préféré.

#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);
}
Démon de l'ombre
la source
Avez-vous lu quelque chose comme n3337 sur C ++ 11 ? Cela devrait être pertinent pour votre question! Avez-vous envisagé d'utiliser std::arrayou std::vector....
Basile Starynkevitch
@BasileStarynkevitch Je n'ai pas lu ça. Le code apparaît dans une bibliothèque tierce. Je veux juste comprendre le sens.
Démon fantôme
Voir aussi norvig.com/21-days.html pour un aperçu utile (et regardez qui est l'auteur de cette page).
Basile Starynkevitch du
2
Ressemble à un doublon de stackoverflow.com/questions/6106158/…
sharptooth
@BasileStarynkevitch Je ne comprends pas la pertinence de ce lien.
Courses de légèreté en orbite

Réponses:

86

C'est en fait très difficile à expliquer, mais je vais essayer ...

Tout d'abord, dimofvous indique la dimension ou le nombre d'éléments dans un tableau. (Je crois que "dimension" est la terminologie préférée dans les environnements de programmation Windows).

Ceci est nécessaire car C++et Cne vous donne pas de méthode native pour déterminer la taille d'un tableau.


Souvent, les gens supposent que sizeof(myArray)cela fonctionnera, mais cela vous donnera en fait la taille en mémoire, plutôt que le nombre d'éléments. Chaque élément prend probablement plus d'un octet de mémoire!

Ensuite, ils pourraient essayer sizeof(myArray) / sizeof(myArray[0]). Cela donnerait la taille en mémoire du tableau, divisée par la taille du premier élément. C'est ok, et largement utilisé dans le Ccode. Le problème majeur avec cela est qu'il semble fonctionner si vous passez un pointeur au lieu d'un tableau. La taille d'un pointeur en mémoire sera généralement de 4 ou 8 octets, même si ce qu'il désigne pourrait être un tableau de 1000 éléments.


Donc, la prochaine chose à essayer C++est d'utiliser des modèles pour forcer quelque chose qui ne fonctionne que pour les tableaux, et donnera une erreur de compilation sur un pointeur. Cela ressemble à ceci:

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

Le modèle ne fonctionnera qu'avec un tableau. Il déduira le type (pas vraiment nécessaire, mais doit être là pour que le modèle fonctionne) et la taille du tableau, puis il retourne la taille. La façon dont le modèle est écrit ne peut pas fonctionner avec un pointeur.

Habituellement, vous pouvez vous arrêter ici, et cela se trouve dans le C ++ Standard Libary as std::size.


Attention: ci-dessous, il pénètre dans le territoire de l'avocat de la langue poilue.


C'est assez cool, mais échoue toujours dans un cas de bord obscur:

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

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

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

Notez que le tableau xest déclaré , mais non défini . Pour appeler une fonction (ie ArraySize) avec elle, xdoit être défini .

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

Vous ne pouvez pas lier cela.


Le code que vous avez dans la question est un moyen de contourner cela. Au lieu d'appeler réellement une fonction, nous déclarons une fonction qui retourne un objet exactement de la bonne taille . Ensuite, nous utilisons l' sizeofastuce à ce sujet.

Il semble que nous appelions la fonction, mais sizeofest purement une construction de compilation, donc la fonction n'est jamais réellement appelée.

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

Notez que vous ne pouvez pas réellement renvoyer un tableau à partir d'une fonction, mais vous pouvez renvoyer une référence à un tableau.

Ensuite , DimofSizeHelper(myArray)est une expression dont le type est un tableau sur l' N charart. L'expression n'a pas besoin d'être exécutable, mais elle a du sens au moment de la compilation.

Par conséquent sizeof(DimofSizeHelper(myArray)), vous indiquera la taille au moment de la compilation de ce que vous obtiendriez si vous appeliez réellement la fonction. Même si nous ne l'appelons pas vraiment.

Austin Powers, les yeux croisés


Ne vous inquiétez pas si ce dernier bloc n'a aucun sens. C'est une astuce bizarre pour contourner un cas de bord bizarre. C'est pourquoi vous n'écrivez pas vous-même ce type de code et laissez les développeurs de bibliothèques s'inquiéter de ce genre de non-sens.

BoBTFish
la source
3
@Shadowfiend C'est aussi faux. Les choses sont encore plus laides que ça, parce que ce n'est pas en fait une déclaration d'une fonction, c'est une déclaration d'une référence de fonction ... Je suis toujours en train de trouver comment expliquer cela.
BoBTFish
5
Pourquoi c'est une déclaration d'une référence de fonction? Le "&" avant "DimofSizeHelper" signifie que le type de retour est char (&) [N], selon la réponse de bolov.
Démon fantôme
3
@Shadowfiend Absolument raison. Je parlais juste des ordures parce que mon cerveau était noué.
BoBTFish
La dimension n'est pas le nombre d'éléments dans un tableau. Autrement dit, vous pouvez avoir 1, 2, 3 ou des tableaux de dimension supérieure, qui pourraient chacun avoir le même nombre d'éléments. Par exemple array1D [1000], array 2D [10] [100], array3D [10] [10] [10]. ayant chacun 1000 éléments.
jamesqf
1
@jamesqf Dans des langages comme C ++, un tableau multidimensionnel est simplement un tableau qui contient d'autres tableaux. Du point de vue du compilateur, le nombre d'éléments dans le tableau principal est souvent totalement indépendant de son contenu - qui peut être des tableaux secondaires ou tertiaires.
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];

DimofSizeHelperest une fonction modèle qui prend un T(&)[N]paramètre - alias une référence à un C-tableau de N éléments de type Tet renvoie un char (&)[N]aka une référence à un tableau de N caractères. En C ++, un caractère est un octet déguisé et sizeof(char)est garanti 1par la norme.

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

nse voit attribuer la taille du type de retour de DimofSizeHelper, sizeof(char[N])qui est N.


C'est un peu compliqué et inutile. La façon habituelle de le faire était:

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

Depuis C ++ 17, cela n'est également pas nécessaire, comme nous l'avons std::sizefait, mais de manière plus générique, être en mesure d'obtenir la taille de n'importe quel conteneur de style stl.


Comme l'a souligné BoBTFish, il est nécessaire pour un cas de bord.

bolov
la source
2
C'est nécessaire si vous ne pouvez pas utiliser ODR le tableau dont vous voulez prendre la taille (il est déclaré mais non défini). Certes, assez obscure.
BoBTFish
Merci d'avoir expliqué le type dans la fonction de modèle. Ça aide vraiment.
Démon fantôme
3
Nous avons std::extentdepuis C ++ 11 qui est le temps de compilation.
LF