Jak działa ten kod szablonu, aby uzyskać rozmiar tablicy?

61

Zastanawiam się, dlaczego ten rodzaj kodu może uzyskać rozmiar tablicy testowej? Nie znam gramatyki w szablonie. Może ktoś mógłby wyjaśnić znaczenie kodu pod template<typename,size_t>. Poza tym preferowany jest również link referencyjny.

#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);
}
Cień diabeł
źródło
Czy czytałeś coś takiego jak n3337 na temat C ++ 11 ? Powinien być odpowiedni dla twojego pytania! Czy zastanawiałeś się nad użyciem std::arraylub std::vector...
Basile Starynkevitch
@BasileStarynkevitch Nie przeczytałem tego. Kod pojawia się w bibliotece innej firmy. Chcę tylko zrozumieć znaczenie.
Shadow fiend,
Zobacz także norvig.com/21-days.html, aby uzyskać przydatne informacje (i zobacz, kto jest autorem tej strony).
Basile Starynkevitch
2
Wygląda jak duplikat stackoverflow.com/questions/6106158/…
sharptooth
@BasileStarynkevitch Nie rozumiem znaczenia tego linku.
Wyścigi lekkości na orbicie

Odpowiedzi:

86

To jest naprawdę trudne do wyjaśnienia, ale spróbuję ...

Po pierwsze, dimofinformuje o wymiarze lub liczbie elementów w tablicy. (Uważam, że „wymiar” jest preferowaną terminologią w środowiskach programistycznych Windows).

Jest to konieczne, ponieważ C++i Cnie daje natywnego sposobu określania rozmiaru tablicy.


Często ludzie zakładają, sizeof(myArray)że zadziała, ale w rzeczywistości da ci rozmiar pamięci, a nie liczbę elementów. Każdy element prawdopodobnie zajmuje więcej niż 1 bajt pamięci!

Następnie mogą spróbować sizeof(myArray) / sizeof(myArray[0]). Dałoby to rozmiar w pamięci tablicy podzielony przez rozmiar pierwszego elementu. Jest ok i szeroko stosowany w Ckodzie. Głównym problemem jest to, że wydaje się działać, jeśli przekażesz wskaźnik zamiast tablicy. Rozmiar wskaźnika w pamięci zwykle wynosi 4 lub 8 bajtów, nawet jeśli wskazuje na tablicę zawierającą 1000 elementów.


Następną rzeczą do wypróbowania C++jest użycie szablonów, aby wymusić coś, co działa tylko dla tablic i da błąd kompilatora na wskaźniku. To wygląda tak:

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

Szablon będzie działał tylko z tablicą. Wyliczy typ (nie jest tak naprawdę potrzebny, ale musi tam być, aby szablon działał) i rozmiar tablicy, a następnie zwróci rozmiar. Sposób pisania szablonu nie może działać ze wskaźnikiem.

Zwykle możesz się tutaj zatrzymać, a jest to w standardowej wersji biblioteki C ++ jako std::size.


Ostrzeżenie: poniżej trafia na owłosione terytorium prawnika języka.


Jest to całkiem fajne, ale nadal nie działa w przypadku niejasnej krawędzi:

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

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

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

Zauważ, że tablica xjest zadeklarowana , ale nie zdefiniowana . Aby wywołać z nim funkcję (tj. ArraySize), xNależy ją zdefiniować .

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

Nie możesz tego połączyć.


Kod, który masz w pytaniu, pozwala to obejść. Zamiast faktycznie wywoływać funkcję, deklarujemy funkcję, która zwraca obiekt o dokładnie odpowiednim rozmiarze . Następnie wykorzystujemy sizeofsztuczkę.

To wygląda jak nazywamy funkcję, ale sizeofjest czysto czas kompilacji konstrukt, więc funkcja nigdy nie jest wywoływana.

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

Zauważ, że faktycznie nie możesz zwrócić tablicy z funkcji, ale możesz zwrócić odwołanie do tablicy.

Następnie DimofSizeHelper(myArray)jest wyrażenie, którego typ jest tablicą na N chars. Wyrażenie tak naprawdę nie musi być dostępne, ale ma sens w czasie kompilacji.

Dlatego sizeof(DimofSizeHelper(myArray))powie ci rozmiar w czasie kompilacji, co byś otrzymał, gdybyś faktycznie wywołał funkcję. Chociaż tak naprawdę nie nazywamy tego.

Austin Powers ze skrzyżowanymi oczami


Nie martw się, jeśli ten ostatni blok nie miałby sensu. Dziwne jest obejście dziwacznej obudowy. Dlatego nie piszesz tego rodzaju kodu samemu i pozwalasz programistom bibliotek martwić się o tego rodzaju bzdury.

BoBTFish
źródło
3
@Shadowfiend To także źle. Sprawy są jeszcze brzydsze, ponieważ tak naprawdę nie jest to deklaracja funkcji, to deklaracja odwołania do funkcji ... Nadal zastanawiam się, jak to wyjaśnić.
BoBTFish
5
Dlaczego jest to deklaracja odwołania do funkcji? „&” Przed „DimofSizeHelper” oznacza, że ​​typem zwracanym jest char (&) [N], zgodnie z odpowiedzią bolov.
Shadow fiend,
3
@Shadowfiend Absolutnie racja. Mówiłem tylko o śmieciach, bo mój mózg był związany.
BoBTFish
Wymiar nie jest liczbą elementów w tablicy. Oznacza to, że możesz mieć tablice o wymiarach 1, 2, 3 lub wyższych, z których każda może mieć taką samą liczbę elementów. Np. Tablica 1D [1000], tablica 2D [10] [100], tablica 3D [10] [10] [10]. każdy mający 1000 elementów.
jamesqf
1
@jamesqf W językach takich jak C ++ tablica wielowymiarowa to po prostu tablica zawierająca inne tablice. Z punktu widzenia kompilatora liczba elementów w tablicy podstawowej jest często całkowicie niezwiązana z jej zawartością - które mogą być tablicami pomocniczymi lub trzeciorzędnymi.
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];

DimofSizeHelperto funkcja szablonu, która pobiera T(&)[N]parametr - aka odwołanie do tablicy C N elementów typu Ti zwraca char (&)[N]aka referencję do tablicy N znaków. W C ++ char jest bajtem w przebraniu i sizeof(char)gwarantuje, że będzie 1zgodny ze standardem.

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

nma przypisany rozmiar typu zwracanego DimofSizeHelper, czyli sizeof(char[N])który jest N.


To jest trochę skomplikowane i niepotrzebne. Zwykłym sposobem na to było:

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

Od C ++ 17 jest to również niepotrzebne, ponieważ mamy to, std::sizeco robi, ale w bardziej ogólny sposób, będąc w stanie uzyskać rozmiar dowolnego kontenera w stylu stl.


Jak wskazał BoBTFish, jest to konieczne w przypadku krawędzi.

bolov
źródło
2
Jest to konieczne, jeśli nie możesz użyć ODR-tablicy, której rozmiar chcesz przyjąć (jest zadeklarowany, ale nie zdefiniowany). Trzeba przyznać, że dość niejasne.
BoBTFish
Dziękujemy za wyjaśnienie typu w funkcji szablonu. To naprawdę pomaga.
Shadow fiend,
3
Mamy std::extentod C ++ 11, który jest czasem kompilacji.
LF