LINUX.ORG.RU

[C++] Статические константы класса в выражениях

 


0

1

Встретился с непонятным поведением в такой ситуации:

class Foo
{
private:
  static const int kA = 1;
  static const int kB = 2;

public:
  int f(bool b) const { return (b ? kA : kB); }
  int ga() const { return kA; }
  int gb() const { return kB; }
};


int main()
{
  Foo foo;
  std::cout << foo.f(true) << foo.f(false) << foo.ga() << foo.gb() << std::endl;
}

При сборке компилятор ругается на то, что kA и kB не определены в функции Foo::f(bool) const. Почему? Почему эти же константы оказываются определены в функциях ga() и gb()? И почему в выражениях (b ? 0 : kB) и (b ? int(kA) : kB) эти константы тоже определены?


ругается линкер, а не компилятор, ибо статические члены нужно определять вне класса (в классе ты их объявил)

class Foo
{
private:
  static const int kA;
  static const int kB;

public:
  int f(bool b) const { return (b ? kA : kB); }
  int ga() const { return kA; }
  int gb() const { return kB; }
};

const int Foo::kA = 1;
const int Foo::kB = 2;

int main()
{
  Foo foo;
  std::cout << foo.f(true) << foo.f(false) << foo.ga() << foo.gb() << std::endl;
}
vvviperrr ★★★★★
()

Присоединяюсь к вопросу? Почему линковщик ругается?

Obey-Kun ★★★★★
()
Ответ на: комментарий от vvviperrr

В классе я их не только объявил, но и определил. Почему компоновщик ругается на функцию f(), но не на функции ga() и gb(), где эти же константы используются и без раздельного объявления и определения?

ilias
() автор топика
Ответ на: комментарий от UVV

~/tmp$ g++ -Wall -ansi -pedantic ./t.cpp
/tmp/ccCHqHxn.o: In function `Foo::f(bool) const':
t.cpp:(.text._ZNK3Foo1fEb[Foo::f(bool) const]+0x15): undefined reference to `Foo::kA'
t.cpp:(.text._ZNK3Foo1fEb[Foo::f(bool) const]+0x1d): undefined reference to `Foo::kB'
collect2: ld returned 1 exit status

ilias
() автор топика
Ответ на: комментарий от ilias

> В классе я их не только объявил, но и определил.

В твоем примере нет определения.

anonymous
()
Ответ на: комментарий от ilias

ругается линкер, а не компилятор, ибо статические члены нужно определять вне класса (в классе ты их объявил)

Почему ты не внял данному тебе совету?

UVV ★★★★★
()

Потому что в С++ допускается опускать определения статических констант интегральных типов до тех пор пока оно не требуется компилятору.

В твоем примере как раз и нет определений. Компилятор в случае ga() и gb() обошелся без их определений (заинлайнил константу), а в случае f() решил что нужно определение.

PS: в VC этот код успешно компилируется и работает

anonymous
()
Ответ на: комментарий от UVV

> Почему ты не внял данному тебе совету?

Потому что я просил объяснить поведение компилятора, а не способа любой ценой собрать программу.

ilias
() автор топика
Ответ на: комментарий от ilias

потому, что собирал в выключенной оптимизацией и были обращения к значениям по внешним символам. собирай с -О1/-О2/-О3. такой код противоречит стандарту, т.к. подобная оптимизация допускается, но не требуется.

arsi ★★★★★
()
Ответ на: комментарий от arsi

> ISO/IEC 14882:2003, раздел 9.4.2, пункты 2 и 4.

благодарю

Obey-Kun ★★★★★
()
Ответ на: комментарий от arsi

> такой код противоречит стандарту, т.к. подобная оптимизация допускается, но не требуется.

Почему противоречит? If a static data member is of const integral or const enumeration type, its declaration in the class definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that case, the member can appear in integral constant expressions.

Или (b ? kA : kB) не является константным выражением?

ilias
() автор топика
Ответ на: комментарий от ilias

> Или (b ? kA : kB) не является константным выражением?

Как сие выражение может быть константным? Мозг хоть иногда включать надо.

anonymous
()
Ответ на: комментарий от ilias

> Или (b ? kA : kB) не является константным выражением?

в данном случае не требуется, чтобы это выражение было константным, так что на усмотрение компилятора. в процитированном тексте говорится, что такие константы можно использовать там, где необходимо именно константное выражение:

class Foo {
  // ...
  static const int kA = 1;
  static const int kB = kA + 1; // <--- требуется константное выражение
  // ...
};

кроме того, в этом абзаце после процитированного текста есть ещё одно предложение ;)

arsi ★★★★★
()
Ответ на: комментарий от arsi

Хорошо, тогда почему (b ? kA : kB) требует определения констант, а

int h(int a, int b) const { return a * kA + b * kB; }

нет?

ilias
() автор топика

месяц С++ на лоре? Сессия?

Почему люди не могут самостоятельно взять любую вменяемую литературу по языку С++, а сразу идут и просят подумать за них, поискать за них, объяснить им?

anonymous
()

IMHO бага, других разумных объяснений не нахожу. При сборке g++ с оптимизацией собирается молча, даже ворнинга даже с -Wall не выдает.

AIv ★★★★★
()
Ответ на: комментарий от AIv

> IMHO бага

Или фича. Таки да, с -O[123] на g++ (Debian 4.4.5-10) 4.4.5 собирается.

По поводу вопроса я вот что думаю: в арифметических выражениях, сравнениях и вызовах функций в данном случае требуется int. operator?: же решает, что в этом случае надо вернуть const int&, что, само собой, требует определения объекта. Таким образом, если я напишу (b ? int(kA) : int(kB)) или (b ? static_cast<const int>(kA) : kB), возвращаемый тип уже не будет ссылкой, и компилятор подставит значение.

ilias
() автор топика
Ответ на: комментарий от ilias

На фичу похоже слабо, все же скорее ИМНО бага, особенность реализации данного компайлера. Выше уже писали что на более свежих версиях собирается все молча.

Да, Ваше объяснение вполне правдопобобно и у меня тоже (gcc version 4.3.1 20080507 (prerelease) [gcc-4_3-branch revision 135036] (SUSE Linux)) с явным приведением к int собирается молча и без оптимизации;-) Но это не дело... могу лишь предположить, что ребята сконцентрировались на оптимизированных режимах, а на обычный малость задвинули;-)

AIv ★★★★★
()
Ответ на: комментарий от AIv

> На фичу похоже слабо, все же скорее ИМНО бага, особенность реализации данного компайлера.

Особенность --- это да. Стандарт не определяет, какой тип должен возвращать operator?:, и в gcc решили так.

ilias
() автор топика

сделай g++ -fdump-tree-all твойфайл.схх и покажи кусок из *.gimple относящийся к f (а можно и весь файл, если не слишком длинный)

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

сделай g++ -fdump-tree-all твойфайл.схх и покажи кусок из *.gimple относящийся к f

Спасибо за отличный совет!

Названия объектов изменены, но суть та же.

g++ -fdump-tree-all ./t.cpp

int C::f(bool) const (const struct C * const this, bool b)
{
  int D.26692;
  const int iftmp.71;

  if (b != 0) goto <D.26694>; else goto <D.26695>;
  <D.26694>:
  iftmp.71 = A;
  goto <D.26696>;
  <D.26695>:
  iftmp.71 = B;
  <D.26696>:
  D.26692 = iftmp.71;
  return D.26692;
}

g++ -fdump-tree-all -O3 ./t.cpp

int C::f(bool) const (const struct C * const this, bool b)
{
  int D.26767;
  const int iftmp.72;

  if (b != 0) goto <D.26769>; else goto <D.26770>;
  <D.26769>:
  iftmp.72 = A;
  goto <D.26771>;
  <D.26770>:
  iftmp.72 = B;
  <D.26771>:
  D.26767 = iftmp.72;
  return D.26767;
}

Что-то я особой разницы не заметил. Там ли я смотрел?

ilias
() автор топика
Ответ на: комментарий от ilias

там и не должно быть разницы,

1. разница может быть с тем жцц, который варнингов не выдает

2. можно проверить, правда ли там ссылка, или все же инт

вот у меня например

int Foo::f(bool) const (this, b)
{
  int D.21152;
  int iftmp.62;

  if (b)
    {
      iftmp.62 = kA;
    }
  else
    {
      iftmp.62 = kB;
    }
  D.21152 = iftmp.62;
  return D.21152;
}

Названия объектов изменены, но суть та же.

зачем изменены? скопипасть отсюда свой код и скомпилируй

хотя разница есть, но видно, что адресов он не берет

www_linux_org_ru ★★★★★
()
Ответ на: комментарий от ilias

а вот вариант когда его просишь сделать ссылку:

int Foo::f(bool) const (this, b)
{
  const int * iftmp.62;
  int D.21154;
  const int & x;

  if (b)
    {
      iftmp.62 = &kA;
    }
  else
    {
      iftmp.62 = &kB;
    }
  x = iftmp.62;
  D.21154 = *x;
  return D.21154;
}
www_linux_org_ru ★★★★★
()
Ответ на: комментарий от www_linux_org_ru

> хотя разница есть, но видно, что адресов он не берет

Честно говоря, никакой разницы кроме как в именах не заметил, если ты говоришь о моих результатах. Но это же несущественно.

ilias
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.