Dzisiaj natknąłem się na ciekawy problem. Rozważmy ten prosty przykład:
template <typename T>
void foo(const T & a) { /* code */ }
// This would also fail
// void foo(const int & a) { /* code */ }
class Bar
{
public:
static const int kConst = 1;
void func()
{
foo(kConst); // This is the important line
}
};
int main()
{
Bar b;
b.func();
}
Podczas kompilacji pojawia się błąd:
Undefined reference to 'Bar::kConst'
Teraz jestem prawie pewien, że dzieje się tak dlatego, że static const int
nigdzie nie jest zdefiniowany, co jest zamierzone, ponieważ zgodnie z moim zrozumieniem kompilator powinien być w stanie dokonać zamiany w czasie kompilacji i nie potrzebować definicji. Jednak ponieważ funkcja przyjmuje const int &
parametr, wydaje się, że nie dokonuje podstawienia, a zamiast tego preferuje odniesienie. Mogę rozwiązać ten problem, wprowadzając następującą zmianę:
foo(static_cast<int>(kConst));
Wydaje mi się, że teraz zmusza to kompilator do utworzenia tymczasowego int, a następnie przekazania do niego odniesienia, co może z powodzeniem wykonać w czasie kompilacji.
Zastanawiałem się, czy było to zamierzone, czy też oczekuję zbyt wiele od gcc, aby poradzić sobie z tym przypadkiem? A może jest to coś, czego z jakiegoś powodu nie powinienem robić?
const int kConst = 1;
ten sam wynik. Poza tym rzadko istnieje powód (nie przychodzi mi do głowy żaden), aby funkcja przyjmowała parametr typuconst int &
- po prostu użyjint
tutaj.static
powoduje błąd `` ISO C ++ zabrania inicjalizacji elementu 'kConst' ... czyniąc 'kConst' statycznym. 'std::min( some_val, kConst)
, gdyżstd::min<T>
posiada parametry typuT const &
, a implikacja jest taka, że musimy przekazać referencję do tej kConst. Okazało się, że wystąpiło to tylko wtedy, gdy optymalizacja była wyłączona. Naprawiono przy użyciu statycznej obsady.Odpowiedzi:
To celowe, 9.4.2 / 4 mówi:
Przekazując statyczny element członkowski danych przez odwołanie do stałej, „używasz” go, 3.2 / 2:
Tak więc w rzeczywistości „używasz” go, gdy przekazujesz go również przez wartość lub w pliku
static_cast
. Po prostu GCC uwolniło cię od haczyka w jednym przypadku, ale nie w drugim.[Edycja: gcc stosuje reguły z wersji roboczych C ++ 0x: "Zmienna lub nie przeciążona funkcja, której nazwa pojawia się jako potencjalnie oceniane wyrażenie, jest używana na zasadzie odr, chyba że jest to obiekt, który spełnia wymagania dotyczące pojawienia się w stałej wyrażenie (5.19) i konwersja lwartości do rwartości (4.1) jest natychmiast stosowana. ”. Rzutowanie statyczne natychmiast wykonuje konwersję lvalue-rvalue, więc w C ++ 0x nie jest „używane”.]
Praktyczny problem z odwołaniem do const polega na tym, że
foo
ma on prawo wziąć adres swojego argumentu i porównać go na przykład z adresem argumentu z innego wywołania, przechowywanego w zmiennej globalnej. Ponieważ statyczny element członkowski danych jest unikalnym obiektem, oznacza to, że jeśli wywołujeszfoo(kConst)
z dwóch różnych jednostek tłumaczeniowych, adres przekazanego obiektu musi być w każdym przypadku taki sam. AFAIK GCC nie może tego zorganizować, chyba że obiekt jest zdefiniowany w jednej (i tylko jednej) jednostce tłumaczeniowej.OK, więc w tym przypadku
foo
jest to szablon, stąd definicja jest widoczna we wszystkich jednostkach tłumaczeniowych, więc być może kompilator teoretycznie mógłby wykluczyć ryzyko, że zrobi cokolwiek z adresem. Ale generalnie na pewno nie powinieneś brać adresów lub odwołań do nieistniejących obiektów ;-)źródło
template <int N> int intvalue() { return N; }
. Wtedy zintvalue<kConst>
,kConst
pojawia się tylko w kontekście wymagającym integralnego stałego wyrażenia, więc nie jest używane. Ale funkcja zwraca obiekt tymczasowy o tej samej wartości cokConst
, i może wiązać się z odwołaniem do stałej. Nie jestem jednak pewien, czy może istnieć prostszy sposób przenoszenia, którykConst
nie jest używany.r = s ? kConst1 : kConst2
) z gcc 4.7. Rozwiązałem to, używając rzeczywistego plikuif
. W każdym razie dzięki za odpowiedź!Jeśli piszesz statyczną zmienną const z inicjatorem wewnątrz deklaracji klasy, to tak, jakbyś pisał
class Bar { enum { kConst = 1 }; }
a GCC potraktuje go w ten sam sposób, co oznacza, że nie ma adresu.
Powinien to być poprawny kod
class Bar { static const int kConst; } const int Bar::kConst = 1;
źródło
To naprawdę ważny przypadek. Zwłaszcza, że foo może być funkcją z STL, taką jak std :: count, która przyjmuje stałą T & jako trzeci argument.
Spędziłem dużo czasu próbując zrozumieć, dlaczego konsolidator miał problemy z tak podstawowym kodem.
Komunikat o błędzie
mówi nam, że linker nie może znaleźć symbolu.
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 U Bar::kConst
Widzimy z 'U', że Bar :: kConst jest niezdefiniowany. Stąd, kiedy linker próbuje wykonać swoją pracę, musi znaleźć symbol. Ale deklarujesz tylko kConst i nie definiujesz go.
Rozwiązaniem w C ++ jest również zdefiniowanie tego w następujący sposób:
template <typename T> void foo(const T & a) { /* code */ } class Bar { public: static const int kConst = 1; void func() { foo(kConst); // This is the important line } }; const int Bar::kConst; // Definition <--FIX int main() { Bar b; b.func(); }
Następnie widać, że kompilator umieści definicję w wygenerowanym pliku obiektowym:
$nm -C main.o 0000000000000000 T main 0000000000000000 W void foo<int>(int const&) 0000000000000000 W Bar::func() 0000000000000000 R Bar::kConst
Teraz możesz zobaczyć „R” mówiącą, że jest zdefiniowana w sekcji danych.
źródło
>nm -C main.o | grep kConst
daje mi tylko jedną linię0000000000400644 R Bar::kConst
.nm -C lib.a
dajeConstants.o: 0000000000000000 R Bar::kConst
imain_file.o: U Bar::kConst ...
.g ++ wersja 4.3.4 akceptuje ten kod (zobacz ten link ). Ale g ++ wersja 4.4.0 odrzuca to.
źródło
Myślę, że ten artefakt C ++ oznacza, że za każdym razem, gdy
Bar::kConst
jest mowa, używana jest jego wartość dosłowna.Oznacza to, że w praktyce nie ma zmiennej, do której można by się odwołać.
Być może będziesz musiał to zrobić:
void func() { int k = kConst; foo(k); }
źródło
foo(static_cast<int>(kConst));
, prawda?Możesz również zastąpić go funkcją składową constexpr:
class Bar { static constexpr int kConst() { return 1; }; };
źródło
Prosta sztuczka: użyj
+
przedkConst
przekazaniem funkcji. Zapobiegnie to pobieraniu odwołania do stałej, a w ten sposób kod nie wygeneruje żądania konsolidatora do obiektu stałego, ale zamiast tego będzie kontynuował z wartością stałą czasu kompilatora.źródło
static const
wartości inicjowanej w deklaracji. Zawsze prowadziłoby to do błędu konsolidatora, a gdy ta sama stała jest również deklarowana oddzielnie w pliku obiektowym, byłby to również błąd. Kompilator jest również w pełni świadomy sytuacji.static_cast<decltype(kConst)>(kConst)
.foo()
pytanie z pierwotnego pytania będzie wymagało adresu i nie ma mechanizmu, aby traktować go jako tymczasową kopię całej tablicy.Doświadczyłem tego samego problemu, o którym wspomniał Cloderic (statyczna stała w operatorze trójskładnikowym:)
r = s ? kConst1 : kConst2
, ale narzekał tylko po wyłączeniu optymalizacji kompilatora (-O0 zamiast -Os). Stało się to na gcc-none-eabi 4.8.5.źródło