Niezdefiniowane odniesienie do static constexpr char []

186

Chcę mieć static const chartablicę w mojej klasie. GCC narzekało i powiedziało mi, że powinienem skorzystać constexpr, chociaż teraz mówi mi, że to niezdefiniowane odniesienie. Jeśli ustawię tablicę jako nie będącą członkiem, wówczas się kompiluje. Co się dzieje?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby
źródło
1
Tylko przeczucie, czy to działa, jeśli baz jest na przykład int? Czy możesz wtedy uzyskać do niego dostęp? Może to być także błąd.
FailedDev
1
@Pubby: Pytanie: W której jednostce tłumaczeniowej zostanie zdefiniowany? Odpowiedź: Wszystko, co zawiera nagłówek. Problem: Narusza zasadę jednej definicji. Wyjątek: całki stałych czasowych kompilacji można „zainicjować” w nagłówkach.
Mooing Duck
Kompiluje się dobrze jako int@MooingDuck. Działa dobrze, gdy nie jest członkiem. Czy to też nie naruszy zasady?
Pubby
@ Pubby8: ints cheat. Jako nie będący członkiem nie powinno to być dozwolone, chyba że zasady ulegną zmianie dla C ++ 11 (możliwe)
Mooing Duck
Biorąc pod uwagę opinie i opinie, pytanie to wymagało bardziej szczegółowej odpowiedzi, którą dodałem poniżej.
Shafik Yaghmour,

Odpowiedzi:

188

Dodaj do pliku CPP:

constexpr char foo::baz[];

Powód: Musisz podać definicję elementu statycznego, a także deklarację. Deklaracja i inicjator wchodzą do definicji klasy, ale definicja elementu musi być osobna.

Kerrek SB
źródło
70
To wygląda dziwnie ... ponieważ wydaje się, że nie dostarcza kompilatorowi pewnych informacji, których wcześniej nie miał ...
vines
32
Wygląda to jeszcze dziwniej, gdy masz deklarację klasy w pliku .cpp! Inicjujesz pole w deklaracji klasy, ale nadal musisz „ zadeklarować ” pole, pisząc constexpr char foo :: baz [] poniżej klasy. Wygląda na to, że programiści używający constexpr mogą kompilować swoje programy, postępując zgodnie z jedną dziwną wskazówką: zadeklaruj to ponownie.
Łukasz Czerwiński
5
@L ŁukaszCzerwinski: Słowo, którego szukasz, to „zdefiniuj”.
Kerrek SB
5
Racja, brak nowych informacji: zadeklaruj użyciedecltype(foo::baz) constexpr foo::baz;
nie-użytkownik
6
jak będzie wyglądać to wyrażenie, jeśli foo jest szablonowane? dzięki.
Hei,
80

C ++ 17 wprowadza zmienne wbudowane

C ++ 17 naprawia ten problem dla constexpr staticzmiennych składowych wymagających definicji poza linią, jeśli była używana odr. Zobacz drugą połowę tej odpowiedzi, aby poznać szczegóły sprzed C ++ 17.

Propozycja P0386 Zmienne wbudowane wprowadza możliwość zastosowania inlinespecyfikatora do zmiennych. W szczególności w tym przypadku constexproznacza to inlinestatyczne zmienne składowe. Wniosek mówi:

Specyfikator wbudowany można zastosować zarówno do zmiennych, jak i do funkcji. Zmienna zadeklarowana inline ma taką samą semantykę jak funkcja zadeklarowana inline: może być zdefiniowana identycznie w wielu jednostkach translacji, musi być zdefiniowana w każdej jednostce translacji, w której jest używana odr, a zachowanie programu wygląda tak, jakby jest dokładnie jedna zmienna.

i zmodyfikowano [basic.def] p2:

Deklaracja jest definicją, chyba że
...

  • deklaruje element danych statycznych poza definicją klasy, a zmienna została zdefiniowana w klasie za pomocą specyfikatora constexpr (to użycie jest przestarzałe; patrz [depr.static_constexpr]),

...

i dodaj [depr.static_constexpr] :

W celu zapewnienia zgodności z wcześniejszymi międzynarodowymi standardami C ++ element statyczny constexpr może być redundantnie nadpisany poza klasą bez inicjatora. To użycie jest przestarzałe. [Przykład:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - koniec przykładu]


C ++ 14 i wcześniejsze

W C ++ 03 wolno nam udostępniać tylko inicjatory w klasie dla całek const lub typów wyliczania const , w C ++ 11 użycie constexprtego rozszerzenia zostało rozszerzone na typy dosłowne .

W C ++ 11 nie musimy podawać definicji zakresu przestrzeni nazw dla elementu statycznego, constexprjeśli nie jest on używany odr , możemy to zobaczyć w części roboczej standardowej sekcji C ++ 11 9.4.2 [class.static.data], która mówi: ( podkreślenie moje idzie do przodu ):

[...] Statyczny element danych typu literalnego można zadeklarować w definicji klasy za pomocą specyfikatora constexpr; jeżeli tak, w jej deklaracji należy podać inicjator nawiasowy lub równy, w którym każda klauzula inicjalizująca, która jest wyrażeniem przypisania, jest wyrażeniem stałym. [Uwaga: W obu tych przypadkach element członkowski może pojawiać się w stałych wyrażeniach. —Wskazówka] Członek nadal będzie zdefiniowany w zakresie przestrzeni nazw, jeśli jest używany w programie odr (3.2) , a definicja zakresu przestrzeni nazw nie będzie zawierała inicjatora.

Pojawia się więc pytanie baz : używa się tutaj odr :

std::string str(baz); 

a odpowiedź brzmi tak , dlatego potrzebujemy również definicji zakresu przestrzeni nazw.

Jak więc ustalić, czy zmienna jest używana odr ? Oryginalne sformułowanie C ++ 11 w sekcji 3.2 [basic.def.odr] mówi:

Wyrażenie jest potencjalnie oceniane, chyba że jest nieocenionym operandem (klauzula 5) lub jego podwyrażeniem. Zmienna, której nazwa pojawia się jako potencjalnie ocenione wyrażenie, jest używana odr, chyba że jest to obiekt, który spełnia wymagania dotyczące pojawienia się w wyrażeniu stałym (5.19) i natychmiast stosowana jest konwersja wartości z wartości na wartość (4.1) .

Tak więc baznie uzyskując ekspresję stały ale lwartość-to-rvalue konwersji nie jest natychmiast stosowany, ponieważ nie stosuje się z powodu bazjest tablicą. Jest to omówione w sekcji 4.1 [conv.lval], która mówi:

Glvalue (3.10) niefunkcjonalnego typu innego niż tablica T można przekonwertować na wartość 53. [...]

Co jest stosowane w konwersji tablica na wskaźnik .

To sformułowanie [basic.def.odr] zostało zmienione z powodu Raportu Defektu 712, ponieważ niektóre przypadki nie były objęte tym sformułowaniem, ale zmiany te nie zmieniają wyników dla tego przypadku.

Shafik Yaghmour
źródło
więc jasne jest, że constexprabsolutnie nie ma to z tym nic wspólnego? (i tak bazjest wyrażeniem ciągłym)
MM
@MattMcNabb dobrze constexpr jest wymagany, jeśli element członkowski nie jest, integral or enumeration typeale inaczej, tak, ważne jest to, że jest to wyrażenie stałe .
Shafik Yaghmour,
Wydaje mi się, że w pierwszym akapicie „ord-used” powinien brzmieć jako „odr-used”, ale nigdy nie jestem pewien z C ++
Egor Pasko
37

Jest to naprawdę wada w C ++ 11 - jak wyjaśnili inni, w C ++ 11 statyczna zmienna członkowska constexpr, w przeciwieństwie do każdego innego rodzaju zmiennej globalnej constexpr, ma zewnętrzne powiązanie, dlatego musi być gdzieś wyraźnie zdefiniowana.

Warto również zauważyć, że w praktyce często można uniknąć statycznych zmiennych składowych constexpr bez definicji podczas kompilacji z optymalizacją, ponieważ mogą one być wstawiane we wszystkich zastosowaniach, ale jeśli kompilujesz bez optymalizacji, często twój program nie będzie łączył się. To sprawia, że ​​jest to bardzo popularna ukryta pułapka - twój program dobrze kompiluje się z optymalizacją, ale jak tylko wyłączysz optymalizację (być może w celu debugowania), nie łączy się.

Dobra wiadomość - ta usterka została naprawiona w C ++ 17! Podejście to jest jednak nieco skomplikowane: w C ++ 17 statyczne zmienne constexpr są domyślnie wbudowane . Po inline stosowany do zmiennych jest nową koncepcją w C ++ 17, ale w rzeczywistości oznacza, że nie trzeba nigdzie wyraźnej definicji.

SethML
źródło
4
Do informacji o C ++ 17. Możesz dodać te informacje do zaakceptowanej odpowiedzi!
SR
5

Czy bardziej eleganckim rozwiązaniem nie jest zmiana char[]na:

static constexpr char * baz = "quz";

W ten sposób możemy mieć definicję / deklarację / inicjalizator w 1 linii kodu.

deddebme
źródło
9
z char[]możesz użyć, sizeofaby uzyskać długość łańcucha w czasie kompilacji, z char *nie możesz (zwróci szerokość typu wskaźnika, w tym przypadku 1).
gnzlbg
2
Powoduje to również generowanie ostrzeżenia, jeśli chcesz zachować ścisłą zgodność z ISO C ++ 11.
Shital Shah,
Zobacz moją odpowiedź, która nie pokazuje sizeofproblemu, i może być używana w rozwiązaniach „tylko w nagłówku”
Josh Greifer
4

Moim obejściem dla zewnętrznego połączenia elementów statycznych jest użycie constexprreferencyjnych elementów pobierających (które nie napotykają na problem @gnzlbg podniesiony jako komentarz do odpowiedzi z @deddebme).
Ten idiom jest dla mnie ważny, ponieważ nienawidzę posiadania wielu plików .cpp w moich projektach i staram się ograniczyć liczbę do jednego, który składa się wyłącznie z #includes i main()funkcji.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
Josh Greifer
źródło
-1

W moim środowisku wersja gcc ma wersję 5.4.0. Dodanie „-O2” może naprawić ten błąd kompilacji. Wygląda na to, że gcc może obsłużyć ten przypadek, gdy prosi o optymalizację.

Haishan Zhou
źródło