Невизначена посилання на статичну таблицю constexpr []


186

Я хочу мати static const charмасив у своєму класі. GCC поскаржився і сказав мені, що я повинен використовувати constexpr, хоча зараз він говорить мені, що це невизначене посилання. Якщо я зроблю масив не членом, то він компілюється. Що відбувається?

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

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}

1
Лише підказка, чи це працює, якщо, наприклад, baz int? Ви можете отримати доступ до нього? Це також може бути помилка.
FailedDev

1
@Pubby: Питання: В якій одиниці перекладу вона буде визначена? Відповідь: Все, що включає заголовок. Проблема: порушує одне правило визначення. Виняток: постійні інтеграли часу компіляції можна "ініціалізувати" у заголовках.
Mooing Duck

intСкладає штрафи як @MooingDuck Це чудово працює як не член. Хіба це теж не порушило б це правило?
Паббі

@ Pubby8: ints чіт. Як не член, це не повинно бути дозволено, якщо правила не змінені для C ++ 11 (можливо)
Mooing Duck

З огляду на погляди та відгуки, на це запитання потрібна більш детальна відповідь, яку я додав нижче.
Шафік Ягмур

Відповіді:


188

Додайте до файлу cpp:

constexpr char foo::baz[];

Причина: Ви повинні надати визначення статичного члена, а також декларацію. Декларація та ініціалізатор входять до визначення класу, але визначення члена має бути окремим.


70
Це виглядає дивно ... оскільки він, здається, не надає компілятору деякої інформації, якої раніше не мав ...
vines

32
Це виглядає ще дивніше, коли у вас є декларація класу у файлі .cpp! Ви ініціалізуєте поле в декларації класу, але вам все одно потрібно " оголосити " поле, написавши constexpr char foo :: baz [] під класом. Схоже, програмісти, які використовують constexpr, можуть складати свої програми, дотримуючись однієї дивної поради: оголосити її ще раз.
Лукаш Червінський

5
@LukaszCzerwinski: Слово, яке ви шукаєте, - "визначити".
Керрек СБ

5
Правильно, нової інформації немає: заявіть про використанняdecltype(foo::baz) constexpr foo::baz;
не-користувач

6
як буде виглядати вираз, якщо foo шаблонізовано? Дякую.
Хей

80

C ++ 17 вводить вбудовані змінні

C ++ 17 виправляє цю проблему для constexpr staticзмінних членів, які потребують позалінійного визначення, якщо вона була odr-використана. Дивіться другу половину цієї відповіді, щоб отримати докладніші відомості про попередню C ++ 17.

Пропозиція P0386 Inline Змінні вводить можливість застосувати inlineспецифікатор до змінних. Зокрема, до цього випадку constexprмається inlineна увазі статична змінна елемента. Пропозиція говорить:

Вбудований специфікатор може застосовуватися як до змінних, так і до функцій. Змінна, оголошена вбудованим рядком, має таку саму семантику, що і функція, оголошена вбудованою: вона може бути визначена однаково у кількох одиницях перекладу, повинна визначатися в кожній одиниці перекладу, в якій вона використовується odr, а поведінка програми немовби існує рівно одна змінна.

та змінено [basic.def] p2:

Декларація - це визначення, якщо
...

  • він оголошує статичний член даних поза визначенням класу, і змінна була визначена в класі із специфікатором constexpr (це використання застаріле; див. [depr.static_constexpr]),

...

і додайте [depr.static_constexpr] :

Для сумісності з попередніми C ++ Міжнародними стандартами, статичний член даних constexpr може бути надмірно передекларований за межами класу без ініціалізатора. Це використання застаріло. [Приклад:

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

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

 - кінцевий приклад]


C ++ 14 і раніше

У C ++ 03 нам було дозволено надати ініціалізатори в класі для const інтегралів або типів перерахування const , в C ++ 11, використовуючи constexprце, було поширено на буквальні типи .

У C ++ 11 нам не потрібно давати визначення простору імен для статичного constexprчлена, якщо він не odr-використовується , ми можемо бачити це в проекті стандартного розділу C ++ 11 9.4.2 [class.static.data], який говорить ( наголос минає вперед ):

[...] Статичний член даних буквального типу може бути оголошений у визначенні класу за допомогою специфікатора constexpr; якщо так, то його декларація повинна вказувати дужок або рівний ініціалізатор, у якому кожен ініціалізатор-застереження, яке є призначенням-виразом, є постійним виразом. [Примітка. В обох цих випадках член може відображатися в постійних виразах. —Закінчити примітку] Член все одно повинен бути визначений в області простору імен, якщо він використовується у програмі (3.2), а визначення області простору імен не повинно містити ініціалізатор.

Тоді питання стає, чи baz використовується тут odr :

std::string str(baz); 

і відповідь - так , і тому ми також потребуємо визначення області простору імен.

Тож як ми можемо визначити, чи застосовується змінна odr ? Оригінальне формулювання C ++ 11 у розділі 3.2 [basic.def.odr] говорить:

Експресія потенційно оцінюється, за винятком випадків, коли це неоцінений операнд (п. 5) або його субдекспресія. Змінна, чиє ім'я відображається як потенційно оцінене вираження, застосовується odr, за винятком випадків, коли це об'єкт, який задовольняє вимогам до появи в постійному виразі (5.19), і негайно застосовується перетворення lvalue в rvalue (4.1) .

Таким bazчином, дається постійний вираз, але перетворення lvalue в rvalue не застосовується негайно, оскільки воно не застосовується через bazмасив. Про це йдеться у розділі 4.1 [conv.lval], який говорить:

Glvalue (3.10) нефункціонального типу без масиву T може бути перетворений у перше значення.53 [...]

Що застосовується при перетворенні масив-вказівник .

Це формулювання [basic.def.odr] було змінено завдяки Звіту про дефекти 712, оскільки деякі випадки не були охоплені цим формулюванням, але ці зміни не змінюють результатів для цього випадку.


так нам зрозуміло, що constexprце абсолютно нічого спільного не має? ( bazвсе одно є постійним виразом)
ММ

@MattMcNabb добре constexpr потрібен, якщо член не інший, integral or enumeration typeале в іншому випадку, так, важливо, що це постійний вираз .
Шафік Ягмур

У першому абзаці "ord-used" слід вважати "odr-used", я вважаю, але з C ++ я ніколи не впевнений
Єгор Пасько

37

Це справді недолік у C ++ 11 - як пояснили інші, у C ++ 11 статична змінна члена constexpr, на відміну від усіх інших видів глобальної змінної constexpr, має зовнішню зв'язок, тому повинна десь чітко визначена.

Варто також зазначити, що часто на практиці ви можете відійти від статичних змінних constexpr-членів без визначень при компіляції з оптимізацією, оскільки вони можуть закінчитися вбудованими у всіх напрямках, але якщо ви компілюєте без оптимізації, часто ваша програма не зможе зв’язатися. Це робить це дуже поширеною прихованою пасткою - ваша програма добре поєднується з оптимізацією, але як тільки ви вимкнете оптимізацію (можливо, для налагодження), вона не може зв’язатися.

Хоча гарна новина - ця вада виправлена ​​в C ++ 17! Підхід, однак, дещо зведений: у C ++ 17 статичні змінні члена конспекту є неявними вбудованими . Після рядний застосовується до змінних являє собою нову концепцію в C ++ 17, але це фактично означає , що вони не потребують де - небудь явного визначення.


4
Нагору для C ++ 17 інформації. Ви можете додати цю інформацію до прийнятої відповіді!
SR

5

Хіба не більш елегантним рішенням є зміна char[]на:

static constexpr char * baz = "quz";

Таким чином ми можемо мати визначення / декларацію / ініціалізатор в 1 рядку коду.


9
з char[]ви можете використовувати , sizeofщоб отримати довжину рядка під час компіляції, з char *ви не можете (він повертає ширину вказівного типу 1 , в даному випадку).
gnzlbg

2
Це також генерує попередження, якщо ви хочете бути суворими з ISO C ++ 11.
Shital Shah

Дивіться мою відповідь, яка не виявляє sizeofпроблеми, і її можна використовувати в рішеннях "тільки для заголовка"
Джош

4

Моє вирішення питання щодо зовнішнього зв’язку статичних членів полягає у використанні constexprдовідкових членів (що не стикається з проблемою @gnzlbg, піднятою як коментар до відповіді від @deddebme).
Ця ідіома важлива для мене, тому що я ненавиджу наявність у своїх проектах декількох .cpp-файлів і намагаюся обмежити число одним, яке складається лише з #includes та main()функції.

// 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]'

-1

У моєму середовищі рівень GCC має 5.4.0. Додавання "-O2" може виправити цю компіляційну помилку. Здається, gcc може впоратися з цим випадком, коли просить оптимізувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.