Undefined ссылка на статический constexpr char []

Я хочу иметь массив static const char в моем классе. GCC пожаловался и сказал мне, что я должен использовать constexpr, хотя теперь он сказал мне ссылку undefined. Если я сделаю массив не-членом, тогда он скомпилируется. Что происходит?

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

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

Ответ 1

Добавьте в файл cpp:

constexpr char foo::baz[];

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

Ответ 2

C++ 17 вводит встроенные переменные

C++ 17 исправляет эту проблему для статических переменных-членов constexpr, требующих внешнего определения, если оно было использовано. Смотрите оригинальный ответ ниже для деталей до C++ 17.

Предложение P0386 Inline Variables предоставляет возможность применять встроенный спецификатор к переменным. В частности, в этом случае constexpr подразумевает inline для статических переменных-членов. Предложение говорит:

Встроенный спецификатор может применяться как к переменным, так и к функциям. Переменная, объявленная inline, имеет ту же семантику, что и функция, объявленная inline: она может быть определена идентично в нескольких единицах перевода, должна быть определена в каждой единице перевода, в которой она выделяется, и поведение программы такое, как если бы ровно одна переменная.

и модифицировал [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++ 03 нам было разрешено указывать только в инициализаторах класса константные интегралы или перечислимые типы констант, в C++ 11 с использованием constexpr это было распространено на литеральные типы.

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

[...] Статический член данных литерального типа может быть объявлен в определении класса с помощью спецификатора constexpr; если это так, в его объявлении должна быть указана инициализация-скобка или равный-инициализатор, в которой каждое предложение-инициализатор, являющееся выражением присваивания, является константным выражением. [Примечание: в обоих этих случаях член может появляться в константных выражениях. - примечание конца] Элемент все еще должен быть определен в области пространства имен, если он используется в программе odr (3.2), и определение области пространства имен не должно содержать инициализатор.

Итак, возникает вопрос, является baz ODR используемый здесь:

std::string str(baz); 

и ответ - да, и поэтому нам также требуется определение области имен.

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

Выражение потенциально оценивается, если оно не является неоцененным операндом (раздел 5) или его подвыражением. Переменная, имя которой появляется в качестве потенциально оцениваемого выражения, используется odr, если только она не является объектом, удовлетворяющим требованиям для появления в константном выражении (5.19), и преобразование lvalue-в-значение (4.1) применяется немедленно.

Таким образом, baz дает константное выражение, но преобразование lvalue в rvalue не применяется немедленно, поскольку оно неприменимо, поскольку baz является массивом. Это описано в разделе 4.1 [conv.lval], в котором говорится:

Glvalue (3.10) нефункционального типа, не являющегося массивом T, может быть преобразован в prvalue.53 [...]

Что применяется при преобразовании массива в указатель.

Эта формулировка [basic.def.odr] была изменена из-за Отчета о дефектах 712, так как некоторые случаи не были охвачены этой формулировкой, но эти изменения не изменяют результаты для этого случая.

Ответ 3

Это действительно недостаток в С++ 11, как объяснили другие, в С++ 11 статическая переменная-член constexpr, в отличие от любой другой глобальной переменной constexpr, имеет внешнюю связь, поэтому должна быть явно определена где-то.

Также стоит отметить, что при сборке с оптимизацией вы часто можете избегать статических констант-членов constexpr без определений, так как они могут оказаться вложенными во всех приложениях, но если вы компилируете без оптимизации, часто ваша программа не сможет ссылаться, Это делает это очень распространенной скрытой ловушкой - ваша программа отлично компилируется с оптимизацией, но как только вы отключите оптимизацию (возможно, для отладки), она не связывается.

Хорошая новость: этот недостаток исправлен в С++ 17! Этот подход немного запутан, хотя: в С++ 17 статические константы-члены constexpr неявно встроены. inline применяется к переменным - это новая концепция в С++ 17, но она фактически означает, что они не нуждаются в явном определении в любом месте.

Ответ 4

Не более элегантное решение может изменить char[] на:

static constexpr char * baz = "quz";

Таким образом, мы можем иметь определение/декларация/инициализатор в 1 строке кода.

Ответ 5

Мой обходной путь для внешней constexpr статических членов заключается в использовании constexpr ссылочных членов constexpr (что не приводит к проблеме @gnzlbg, поднятой в качестве комментария к ответу @deddebme).
Эта идиома важна для меня, потому что я ненавижу иметь несколько .cpp файлов в своих проектах и пытаюсь ограничить число до одного, которое состоит только из #include и функции 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]'

Ответ 6

В моем окружении gcc vesion - 5.4.0. Добавление "-O2" может исправить эту ошибку компиляции. Кажется, gcc может справиться с этим случаем при запросе оптимизации.