Неопределенная ссылка на статический constexpr char [] - PullRequest
158 голосов
/ 05 ноября 2011

Я хочу иметь массив 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
}

Ответы [ 6 ]

151 голосов
/ 05 ноября 2011

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

constexpr char foo::baz[];

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

58 голосов
/ 04 марта 2015

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

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

Предложение P0386 Встроенные переменные предоставляет возможность применять встроенный спецификатор к переменным.В частности, в этом случае 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;если это так, в его объявлении должна быть указана инициализация-скобка или равный-инициализатор, в которой каждое предложение инициализатора, являющееся выражением присваивания, является константным выражением[Примечание: в обоих этих случаях член может появляться в константных выражениях.- конец примечания] Элемент по-прежнему должен быть определен в области имен, если он используется в программе (3.2) , и определение области имен не должно содержать инициализатор.

Итак, возникает вопрос: baz odr-used здесь:

std::string str(baz); 

и ответ yes , поэтому нам требуется пространство именопределение области действия.

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

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

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

glvalue (3.10) не функции, не массив типа T можно преобразовать в значение.53 [...]

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

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

31 голосов
/ 13 октября 2017

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

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

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

4 голосов
/ 19 апреля 2016

Разве не более элегантное решение - изменить char[] на:

static constexpr char * baz = "quz";

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

2 голосов
/ 30 августа 2018

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

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

...