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

