17.2 — std::array długość i indeksowanie

W lekcji 16.3 -- std::vector oraz problemu długości bez znaku i indeksu dolnego, omawialiśmy niefortunną decyzję, aby klasy kontenerów bibliotek standardowych używały wartości bez znaku dla długości i indeksów. Ponieważ std::array jest standardową klasą kontenera biblioteki, podlega tym samym problemom.

W tej lekcji podsumujemy sposoby indeksowania i uzyskiwania długości std::array. Ponieważ std::vector i std::array posiadających podobne interfejsy, będzie to równoległe do tego, co omawialiśmy std::vector. Ale ponieważ tylko std::array ma pełne wsparcie dla constexpr, skupimy się na tym trochę więcej.

Przed kontynuowaniem nadszedłby dobry moment, aby odświeżyć pamięć o „konwersje znaków zawężają konwersje, z wyjątkiem sytuacji, gdy constexpr” (zobacz 16.3 -- std::vector oraz problemu długości bez znaku i indeksu dolnego).

Długość std::array ma typ std::size_t

std::array jest zaimplementowany jako struktura szablonu, której deklaracja wygląda następująco:

template<typename T, std::size_t N> // N is a non-type template parameter
struct array;

Jak widzisz, parametr szablonu bez typu reprezentujący długość tablicy (N) ma typ std::size_t). I jak zapewne wiesz teraz std::size_t jest dużym typem całkowitym bez znaku.

Powiązana treść

Szablony klas (w tym szablony struktur) omówimy w lekcji 13.13 — Szablony klas a parametry szablonów nietypowych w lekcji 11.9 — Parametry szablonu inne niż typ.

Zatem, gdy definiujemy a std::array, argument szablonu nietypowego długości musi albo mieć typ std::size_t, albo być konwertowalny na wartość type std::size_t Ponieważ ta wartość musi być constexpr, nie napotykamy problemów z konwersją znaków, gdy używamy wartości całkowitej ze znakiem, ponieważ kompilator chętnie przekonwertuje wartość całkowitą ze znakiem na std::size_t w czasie kompilacji, nie uważając tego za konwersję zawężającą.

Na marginesie…

Przed C++23, C++ nie miał nawet dosłownego sufiksu dla std::size_t, ponieważ niejawna konwersja w czasie kompilacji z int Do std::size_t zwykle wystarcza w przypadkach, gdy potrzebujemy constexpr std::size_t.

Przyrostek został dodany głównie w celu dedukcji typu, ponieważ constexpr auto x { 0 } da ci int a nie a std::size_tW takich przypadkach możliwość odróżnienia 0 (int) od 0UZ (std::size_t) bez konieczności używania jawne static_cast jest przydatne.

Długość i indeksy std::array ma typ size_type, który zawsze std::size_t

Podobnie jak w std::vector, std::array definiuje zagnieżdżony element typedef o nazwie size_type, który jest aliasem dla typu używanego do określenia długości (i indeksów, jeśli są obsługiwane) kontenera. W przypadku std::array, size_type Jest zawsze aliasu dla std::size_t.

Należy pamiętać, że nietypowy parametr szablonu określający długość std::array jest jawnie zdefiniowany jako std::size_t zamiast size_type. Dzieje się tak, ponieważ size_type jest członkiem std::array i nie jest jeszcze zdefiniowany. To jedyne miejsce, w którym używa się std::size_t jawnie -- wszędzie indziej używa się size_type.

Uzyskiwanie długości a std::array

Istnieją trzy popularne sposoby obliczania długości a std::array obiektem.

Po pierwsze możemy zapytać a std::array obiekt o jego długość za pomocą size() funkcji składowej (która zwraca długość jako bez znaku size_type):

#include <array>
#include <iostream>

int main()
{
    constexpr std::array arr { 9.0, 7.2, 5.4, 3.6, 1.8 };
    std::cout << "length: " << arr.size() << '\n'; // returns length as type `size_type` (alias for `std::size_t`)
    return 0;
}

Wypisuje:

length: 5

W przeciwieństwie do std::string i std::string_view, które mają obie length() oraz a size() funkcje członkowskie (które robią to samo rzeczą), std::array (i większość innych typów kontenerów w C++) ma tylko size().

Po drugie, w C++17 możemy użyć std::size() nieelementowej (która for std::array po prostu wywołuje funkcję składową size() , zwracając w ten sposób długość bez znaku size_type).

#include <array>
#include <iostream>

int main()
{
    constexpr std::array arr{ 9, 7, 5, 3, 1 };
    std::cout << "length: " << std::size(arr); // C++17, returns length as type `size_type` (alias for `std::size_t`)

    return 0;
}

W końcu w C++20 możemy użyć std::ssize() funkcja niebędąca członkiem, która zwraca długość jako dużą podpisany typ integralny (zwyklestd::ptrdiff_t):

#include <array>
#include <iostream>

int main()
{
    constexpr std::array arr { 9, 7, 5, 3, 1 };
    std::cout << "length: " << std::ssize(arr); // C++20, returns length as a large signed integral type

    return 0;
}

To jest jedyna funkcja z trzech, która zwraca długość jako typ ze znakiem.

Uzyskiwanie długości a std::array jako wartość constexpr

Ponieważ długość a std::array jest constexpr, każda z powyższych funkcji zwróci długość a std::array jako wartość constexpr (nawet jeśli zostanie wywołana na obiekcie innym niż constexpr std::array )! Oznacza to, że możemy używać dowolnej z tych funkcji w wyrażeniach stałych, a zwróconą długość można niejawnie przekonwertować na int bez konwersji zawężającej:

#include <array>
#include <iostream>

int main()
{
    std::array arr { 9, 7, 5, 3, 1 }; // note: not constexpr for this example
    constexpr int length{ std::size(arr) }; // ok: return value is constexpr std::size_t and can be converted to int, not a narrowing conversion

    std::cout << "length: " << length << '\n';

    return 0;
}

W przypadku użytkowników programu Visual Studio

Visual Studio niepoprawnie wyzwala ostrzeżenie C4365 w powyższym przykładzie. Problem został zgłoszony firmie Microsoft.

Ostrzeżenie

Z powodu wady językowej powyższe funkcje zwrócą wartość inną niż constexpr, gdy zostaną wywołane na std::array parametrze funkcji przekazanym przez odwołanie (const):

#include <array>
#include <iostream>

void printLength(const std::array<int, 5> &arr)
{
    constexpr int length{ std::size(arr) }; // compile error!
    std::cout << "length: " << length << '\n';
}

int main()
{
    std::array arr { 9, 7, 5, 3, 1 };
    constexpr int length{ std::size(arr) }; // works just fine
    std::cout << "length: " << length << '\n';

    printLength(arr);

    return 0;
}

Ten defekt został rozwiązany w C++ 23 przez P2280. W chwili pisania tego tekstu niewiele kompilatorów obsługuje tę funkcję.

Obejście polega na wykonaniu foo() szablon funkcji, w którym długość tablicy jest parametrem szablonu innym niż typ. Tego parametru szablonu innego niż typ można następnie użyć wewnątrz funkcji. Omawiamy to szerzej w lekcji 17.3 — Przekazywanie i zwracanie std::array.

template <auto Length>
void printLength(const std::array<int, Length> &arr)
{
    std::cout << "length: " << Length << '\n';
}

Subscripting std::array za pomocą operator[] lub at() funkcją składową

W poprzedniej lekcji 17.1 — Wprowadzenie do std::array. Omówiliśmy, że najczęstszym sposobem indeksowania a std::array jest użycie operatora indeksu dolnego (operator[]). W tym przypadku nie jest wykonywane żadne sprawdzanie granic, a przekazanie nieprawidłowego indeksu spowoduje niezdefiniowane zachowanie.

Po prostu podobnie jak std::vector, std::array posiada również at() funkcji składowej, która wykonuje indeks dolny ze sprawdzaniem granic w czasie wykonywania. Zalecamy unikać tę funkcję, ponieważ zazwyczaj chcemy sprawdzić granice przed indeksowaniem lub chcemy sprawdzić granice w czasie kompilacji.

Obie te funkcje oczekują, że indeks będzie typu size_type (std::size_t).

Jeśli którakolwiek z tych funkcji zostanie wywołana z wartością constexpr, kompilator wykona konwersję constexpr na std::size_t. Nie jest to uważane za konwersję zawężającą, więc nie napotkasz tutaj problemów ze znakami.

Jeśli jednak którakolwiek z tych funkcji zostanie wywołana z wartością całkowitą niebędącą constexpr, konwersja na std::size_t jest uważana za zawężającą i kompilator może wygenerować ostrzeżenie w tym przypadku dalej (użycie std::vector) w lekcja 16.3 -- std::vector oraz problemu długości bez znaku i indeksu dolnego.

std::get() powoduje czas kompilacji sprawdzanie granic dla indeksów constexpr

Ponieważ długość a std::array jest constexpr, jeśli nasz indeks jest również wartością constexpr, to kompilator powinien być w stanie sprawdzić w czasie kompilacji, czy nasz indeks constexpr mieści się w granicach tablicy (i zatrzymać kompilację, jeśli indeks constexpr jest poza zakresem).

Jednak operator[] z definicji nie sprawdza żadnych granic, oraz at() funkcja członkowska sprawdza tylko granice w czasie wykonywania, a parametry funkcji nie mogą być constexpr (nawet w przypadku funkcji constexpr lub consteval), więc jak w ogóle przekazać indeks constexpr?

Aby sprawdzić granice czasu kompilacji, gdy mamy indeks constexpr, możemy użyć szablonu funkcji std::get() , który przyjmuje indeks jako szablon nietypowy. argument:

#include <array>
#include <iostream>

int main()
{
    constexpr std::array prime{ 2, 3, 5, 7, 11 };

    std::cout << std::get<3>(prime); // print the value of element with index 3
    std::cout << std::get<9>(prime); // invalid index (compile error)

    return 0;
}

Wewnątrz implementacji std::get() znajduje się static_assert, który sprawdza, czy argument szablonu niebędący typem jest mniejszy niż długość tablicy. Jeśli tak nie jest, static_assert zatrzyma proces kompilacji z powodu błędu kompilacji.

Ponieważ argumenty szablonu muszą być constexpr, std::get() można wywołać tylko za pomocą constexpr indeksy.

Czas quizu

Pytanie nr 1

Zainicjuj a std::array o wartości: „h”, „e”, „l”, „l”, „o”. Wydrukuj długość tablicy, a następnie za pomocą operator[], at() i std::get() wypisz wartość elementu o indeksie 1.

Program powinien wydrukować:

The length is 5
eee

Pokaż rozwiązanie

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
22 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze