В классе static const ODR

Меня немного смущает static инициализация в классе члена const. Например, в приведенном ниже коде:

#include <iostream>

struct Foo
{
    const static int n = 42;
};

// const int Foo::n; // No ODR

void f(const int& param)
{
    std::cout << param << std::endl;
}

int g(const int& param)
{
    return param;
}

template<int N>
void h()
{
    std::cout << N << std::endl;
}

int main()
{
    // f(Foo::n); // linker error, both g++/clang++
    std::cout << g(Foo::n) << std::endl; // OK in g++ only with -O(1,2 or 3) flag, why?!
    h<Foo::n>(); // this should be fine
}

Живой пример

Foo::n не определяю (строка закомментирована). Итак, я ожидаю, что вызов f(Foo::n) завершится ошибкой во время компоновки, и это действительно так. Однако следующая строка std::cout << g(Foo::n) << std::endl; компилируется и компонуется нормально только с помощью gcc (clang по-прежнему выдает ошибку компоновщика) всякий раз, когда я использую флаг оптимизации, такой как -O1/2/3.

  1. Почему gcc (пробовали с gcc 5.2.0 и gcc 4.9.3) компилирует и связывает код, когда оптимизация включена?
  2. И правильно ли я говорю, что члены static const внутри класса используются только в константных выражениях, таких как параметры шаблона, как в вызове h<Foo::n>, и в этом случае код должен быть связан?

person vsoftco    schedule 27.09.2015    source источник
comment
нарушения ODR не требуют диагностики.   -  person Shafik Yaghmour    schedule 28.09.2015
comment
Это конкретно вопрос С++ 11? Цитаты odr немного меняются между С++ 11 и С++ 14, хотя я не думаю, что это действительно имеет значение в этом случае, но в некоторых случаях это так.   -  person Shafik Yaghmour    schedule 28.09.2015
comment
@ShafikYaghmour Я скомпилировал с использованием -std=c++11, но сам по себе вопрос не обязательно касается только C++11 (или C++14). O наблюдал такое же поведение и с -std=c++98.   -  person vsoftco    schedule 28.09.2015


Ответы (4)


Нарушения ODR не требуют диагностики, из черновика стандартного раздела C++ 3.2 [basic.def.odr] (выделено мной в дальнейшем):

Каждая программа должна содержать ровно одно определение каждой не встроенной функции или переменной, которая используется в этой программе odr; диагностика не требуется.

Таким образом, непоследовательное поведение на разных уровнях оптимизации является совершенно совместимым поведением.

Неформально переменная является odr-used, если:

берется ее адрес или к ней привязывается ссылка, и функция используется odr, если к ней выполняется вызов функции или берется ее адрес. Если объект или функция используются odr, их определение должно существовать где-то в программе; нарушение этого является ошибкой времени компоновки.

Таким образом, и f, и g будут использовать odr и требуют определения.

Соответствующая цитата C++14 по использованию odr будет из раздела [basic.def.odr]:

Переменная x, имя которой появляется как потенциально вычисляемое выражение ex, odr-используется ex, если применение преобразования lvalue-to-rvalue (4.1) к x дает постоянное выражение (5.19), которое не вызывать любые нетривиальные функции и, если x является объектом, ex является элементом набора потенциальных результатов выражения e, где либо к e применяется преобразование lvalue-to-rvalue (4.1), либо e выражение отброшенного значения [...]

Формулировка в C++11 аналогична, изменения от C++11 до C++14 отражены в отчет о дефекте 712.

До С++ 11 это немного больше сложно, но в принципе то же самое для этого случая.

person Shafik Yaghmour    schedule 27.09.2015

Я предполагаю, что при оптимизации компилятор выполняет следующие действия:

  • Значение const static int n встроено везде. Для переменной n не выделяется память, ссылки на нее становятся недействительными. Функция f() нуждается в ссылке на n, чтобы программа не компилировалась.

  • Функция g короткая и простая. Он эффективно встроен и оптимизирован. После оптимизации функция g не нуждается в ссылке на n, она просто возвращает постоянное значение 42.

Решение состоит в том, чтобы определить переменную вне класса:

struct Foo
{
    const static int n;
};

const int Foo::n = 42;
person Andrey Nasonov    schedule 27.09.2015
comment
Альтернативные решения - сделать его constexpr или значение перечисления. constexpr, возможно, лучше, теперь, когда компиляторы в целом поддерживают его, потому что это меньше исходного кода и потому что он хорошо работает в заголовках. - person Cheers and hth. - Alf; 28.09.2015

Формально нарушения ODR представляют собой неопределенное поведение, поэтому компилятор может демонстрировать любое поведение, которое ему нравится. Вот почему поведение меняется с уровнем оптимизации и компилятором - компилятор не обязан поддерживать определенное поведение.

person Puppy    schedule 27.09.2015

Вообще нет определения. GCC 4.9.2 не компилирует и не связывает это ни с какими флагами.

Обратите внимание, что:

const static int n = 42;

является объявлением и инициализатором, но не определением.

person Nevermore    schedule 27.09.2015
comment
Я знаю, что это не определение, однако, как вы можете видеть из живого примера, код скомпилирован и связан. - person vsoftco; 27.09.2015
comment
Это странно. У меня нет gcc4.9.2, но есть 4.9.3 от macports на OS X, и он компилируется и линкуется. - person vsoftco; 27.09.2015
comment
это может быть проблема оптимизатора, но определения до сих пор нет. - person Nevermore; 28.09.2015
comment
Посмотрите здесь: melpon.org/wandbox/permlink/o9HLkpgJAyibg36t Это версия 4.9.2, и она ссылается . И да, я знаю, что это не определение, поэтому я спросил почему gcc компилирует и связывает код? - person vsoftco; 28.09.2015
comment
Этот ответ упускает суть. В некоторых ситуациях набор инструментов не заботится об отсутствующем определении либо потому, что ODR не вызывается, либо потому, что оптимизация сделала его спорным. ОП столкнулся с одним из этих случаев и хотел бы точно знать, что это такое (хотя лично я бы не стал пытаться рационализировать это). - person Lightness Races in Orbit; 28.09.2015
comment
@LightnessRacesinOrbit Я спросил в основном потому, что не уверен, каков вариант использования такой встроенной статической инициализации const. Знаете ли вы какой-либо другой случай, кроме использования параметра шаблона? И я продолжаю задаваться вопросом, почему такое объявление не рассматривается как определение? Из-за проблем с оптимизацией? - person vsoftco; 28.09.2015
comment
@vsoftco: И я цитирую, хотя лично я бы не стал пытаться рационализировать это. - person Lightness Races in Orbit; 28.09.2015
comment
@LightnessRacesinOrbit Наверное, скучное воскресенье для меня ;) - person vsoftco; 28.09.2015
comment
@vsoftco: пример использования — поместить объявление и инициализатор в файл заголовка, а определение — в файл .cpp. Таким образом, все единицы перевода, которые #include заголовки увидят и выиграют от инициализатора (для оптимизации), в то время как будет по-прежнему только одно определение, что предотвратит любые проблемы с связыванием. Это очень прагматично. - person Arne Vogel; 28.09.2015
comment
@ArneVogel Спасибо, это правда. Я хотел сказать, каков вариант использования без определения в файле cpp, если таковой имеется. - person vsoftco; 28.09.2015