Путаница с объявлением и определением статических константных элементов данных

Скотт Мейерс пишет в «Эффективном современном C++», пункт 30, стр. 210, что существует

нет необходимости определять интегральные static const элементы данных в классах; достаточно одних заявлений,

тогда пример кода

class Widget {
  public:
    static const std::size_t MinVals = 28; // MinVals' declaration;
    ...
};
...                                        // no defn. for MinVals
std::vector<int> widgetData;
widgetData.reserve(Widget::MinVals);       // use of MinVals

Я был убежден, что static const std::size_t MinVals = 28; является объявлением, а также определением, поскольку оно дает значение MinVals, но комментарий, кажется, утверждает, что это только объявление; второй комментарий фактически утверждает, что определения нет. Текст после кода действительно читается

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

Что подтверждает, что static const std::size_t MinVals = 28; не является определением, так что я немного запутался.

cppreference мне не очень помогает (выделено жирным курсивом):

Если элемент данных static интегрального или перечисляемого типа объявлен const (а не volatile), его можно инициализировать с помощью инициализатора, в котором каждое выражение является константным выражением, правильно внутри определения класса:

struct X
{
   const static int n = 1;
   const static int m{2}; // since C++11
   const static int k;
};
const int X::k = 3;

но первые две строки в классе выглядят для меня определениями.

То же самое касается следующего примера cppreference:

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m; // X::n and X::m are odr-used
const int X::n;             // … so a definition is necessary
constexpr int X::m;         // … (except for X::m in C++17)

где я бы сказал, что static const int n = 1; - это определение, но это не так, основываясь на предпоследнем комментарии.


person Enlico    schedule 17.05.2020    source источник
comment
Из n4835.pdf [class.static.data] (2) The declaration of a non-inline static data member in its class definition is not a definition [...]. Но (3) непосредственно для целочисленных типов с инициализатором и этот абзац не поможет, если это объявление или определение.   -  person Werner Henze    schedule 17.05.2020
comment
Подумав об этом еще раз, @AndyG, я не могу сказать что-то еще, чтобы прояснить мой вопрос. Название начинается со слов Запутанная информация. Вот и все. Я в замешательстве и прошу помощи. Путаница по поводу [...]. Можете ли вы помочь мне понять это?   -  person Enlico    schedule 17.05.2020
comment
C++17 представляет inline static, и это здорово.   -  person Matthieu M.    schedule 18.05.2020
comment
недавние, связанные   -  person Cee McSharpface    schedule 18.05.2020
comment
Оглядываясь назад, это также имеет отношение.   -  person Enlico    schedule 23.12.2020


Ответы (3)


нет необходимости определять в классах интегральные статические константы-члены данных; одних деклараций достаточно,

Одних только объявлений достаточно, только если этот объект не используется ODR, то есть если член данных не используется в контексте для этого потребуется, чтобы его адрес существовал (например, привязка к ссылке или применение оператора &). Наличие инициализатора не равнозначно определению.

В примере из книги видно, что MinVals не используется ODR, т. е. компилятор может использовать его значение напрямую, без создания объекта в памяти, и поэтому утверждение:

widgetData.reserve(Widget::MinVals);

становится:

widgetData.reserve(28);

Однако если бы в каком-либо другом месте MinVals использовалось ODR, это сделало бы программу неправильной.

Все остальные примеры из cppreference ясно указывают, когда значение используется ODR и требуется определение, а когда нет:

struct X
{
    const static int n = 1;
    const static int m{2}; // since C++11
    const static int k;
};
const int X::k = 3;

n и m — это объявления с инициализаторами. Попытка получить адрес n или m должна завершиться неудачно.

struct X {
    static const int n = 1;
    static constexpr int m = 4;
};
const int *p = &X::n, *q = &X::m;
const int X::n;
constexpr int X::m;

Выражения &X::n и &X::m учитываются как использование ODR n и m соответственно (то есть запрашивается адрес). Для constexpr статических членов данных требовалось определение до C++17. Начиная с C++17, static constexpr членов данных неявно являются inline, что означает, что определение вне класса не требуется, поскольку они сами являются определениями.

person Piotr Skotnicki    schedule 17.05.2020
comment
Ключевой фразой здесь является объявление инициализации. - person Davis Herring; 17.05.2020
comment
Ответ принят, но у меня есть два сомнения по поводу самого первого абзаца вашего ответа. Вы пишете Одних деклараций достаточно, только если [...]; Вы имеете в виду конкретно объявления _ целочисленных статических константных элементов данных_ или объявления вообще? Вы заканчиваете абзац фразой Наличие инициализатора не равно определению, что делает его похожим на следствие того, что ему предшествует, но я не вижу связи. Может быть, вы можете пояснить этот первый абзац? - person Enlico; 18.05.2020
comment
Это зависит от того, о каких декларациях вы спрашиваете. Вы можете свободно не определять статический член данных (константный или нет) и объявлять его только до тех пор, пока вы не используете его в ODR. Вы можете свободно не определять функцию, пока она не вызывается. Вы можете свободно не определять класс, если вы не используете его в контексте, который требует существования его полного определения. Вам разрешено помещать инициализатор в интегральные константные статические члены данных, но это не делает его определением. Это допустимо и позволяет компилятору выполнять оптимизацию, если этот объект не используется ODR. - person Piotr Skotnicki; 19.05.2020
comment
Не зацикливайтесь только на синтаксисе. static const int i = 0; означает что-то другое, будь то в области класса или в глобальной области. В конечном итоге значение имеет семантика, и она является результатом как синтаксиса, так и контекста. - person Piotr Skotnicki; 19.05.2020
comment
И стандарт явно делает его неопределяемым в [basic.def]/p2. 3, если вы не добавили также ключевое слово inline. - person Piotr Skotnicki; 19.05.2020

Глядя на этот проект стандарта, похоже, что ваш пример попадает в серую зону. Хотя нет явного упоминания таких строк, как:

    static const std::size_t MinVals = 28;

Приведен пример, который очень похож:

6.1 Объявления и определения
...
2 Объявление является определением, если
...
2.3 — оно объявляет невстроенные статические данные член в определении класса
...
Пример: все, кроме одного, являются определениями:

int a; // определяет внешнюю
константу int c = 1; // определяет c
...

Второй пример близок к вашему коду, но существенно отличается наличием квалификатора extern. Также обратите внимание, что в приведенном выше описании объявление (по умолчанию) также является определением, если не применяется одно из перечисленных условий; Я бы сказал (хотя я и не специалист по лингвистическому праву), что ни одно из этих условий не выполняется в точности в вашем случае, поэтому ваше заявление является также определением.

ПРИМЕЧАНИЕ. Связанный документ является только черновиком стандарта; обязательно прочитайте «отказ от ответственности» в нижней части первой страницы!

person Adrian Mole    schedule 17.05.2020
comment
Но условие /2.3 здесь действительно применимо! - person Davis Herring; 17.05.2020
comment
@DavisHerring Это моя «серая зона». Я думаю, что это применяется буквально, но термин "встроенный" может быть двусмысленным: то есть либо явный квалификатор inline или "в теле" (код, помещенный внутри определения класса). Хотя, наверное, я только добавляю путаницы. - person Adrian Mole; 17.05.2020
comment
@AdrianMole, поскольку встроенный в 2.3 не отформатирован в коде, я согласен с вами, что не встроенный означает не написанный внутри определения класса, что означало бы, что 2.3 не применимо к моему (ну, книге Скотта Мейерса) случаю. - person Enlico; 17.05.2020
comment
@EnricoMariaDeAngelis Я бы согласился, но, как я уже сказал, я не языковой юрист. Этот разговор может легко стать итеративным, если не рекурсивным. Кроме того, если первые два примера являются определениями, то я действительно не понимаю, почему ваш - нет. - person Adrian Mole; 17.05.2020
comment
@EnricoMariaDeAngelis: стандарт не использует «встроенный» для обозначения «определенный (или инициализированный) в классе». Он также не использует кодовый шрифт для ссылки на свойства (такие как inline или constexpr), которые могут применяться/подразумеваться иначе, чем с помощью ключевого слова. - person Davis Herring; 17.05.2020

Из Стандарта Глава "12.2 .3.2 Статические члены данных":

Член по-прежнему должен быть определен в области пространства имен, если он используется в программе odr, и определение области пространства имен не должно содержать инициализатор.

Используя его, он должен быть определен.

person Dragan    schedule 17.05.2020