Используется ли статическая переменная constexpr odr?
Дать код ниже, это Foo::FOO1
Используется ODR или нет?
#include <iostream>
#include <map>
#include <string>
class Foo
{
public:
static constexpr auto FOO1 = "foo1";
void bar();
};
void Foo::bar()
{
const std::map<std::string, int> m = {
{FOO1, 1},
};
for (auto i : m)
{
std::cout << i.first << " " << i.second << std::endl;
}
}
int main()
{
Foo f;
f.bar();
return 0;
}
Компиляция кода с -O1
или выше, это нормально, но если скомпилировать с -O0
, Я получаю ошибку ниже (см. Пример coliru:
undefined reference to `Foo::FOO1'
что указывает на то, что он используется ODR. Что он?
Я знаю, что приведенный выше код прекрасно с -O, но в реальном (и более сложном) случае:
- Код компилируется и прекрасно связывается с -O2
- Код получает выше
undefined reference
ошибка LinkTimeOptimization (-O2 -flto
)
Так что это указывает на то, что обе оптимизации (-O
) и LinkTimeOptimization (-flto
) повлияет ли правило на использование ODR? Меняется ли это между C++14 и C++17?
1 ответ
Правило: [basic.def.odr] / 4:
Переменная
x
чье имя появляется как потенциально оцененное выражениеex
используется odrex
если только не применяется преобразование lvalue в rvalue вx
дает константное выражение, которое не вызывает никаких нетривиальных функций и, еслиx
это объект,ex
является элементом множества потенциальных результатов выраженияe
где либо преобразование lvalue в rvalue ([conv.lval]) применяется кe
, или жеe
является выражением отброшенного значения ([expr.prop]).
Первая часть явно устраивает (FOO1
является constexpr
Таким образом, преобразование lvalue в rvalue дает постоянное выражение без вызова нетривиальных функций), но является ли второе?
Мы строим map
, Соответствующий конструктор принимает initializer_list<value_type>
то есть initializer_list<pair<const string, int>>
, pair
имеет несколько конструкторов, но тот, который будет вызываться здесь:
template <class U1, class U2>
constexpr pair(U1&& x, U2&& y); // with U1 = char const*&, U2 = int
Важной частью здесь является то, что мы не создаем напрямую string
мы проходим через этот конвертирующий конструктор pair
, который включает в себя обязательную ссылку на FOO1
, Это использование одр. Здесь нет преобразования lvalue в rvalue, и при этом это не выражение отброшенного значения.
По сути, когда вы берете адрес чего-то, это использование odr - оно должно иметь определение. Таким образом, вы должны добавить определение:
constexpr char const* Foo::FOO1;
Обратите внимание, что, с другой стороны, это:
std::string s = FOO1;
не будет использование одр. Здесь мы непосредственно вызываем конструктор, принимающий char const*
параметр, который будет преобразованием lvalue в rvalue.
В C++17 мы получили это новое предложение в [dcl.constexpr]:
Функция или член статических данных, объявленные с помощью спецификатора constexpr, неявно являются встроенной функцией или переменной ([dcl.inline]).
Это ничего не меняет в использовании odr, FOO1
все еще используется в вашей программе. Но это делает FOO1
неявно встроенная переменная, поэтому вам не нужно явно добавлять для нее определение. Довольно круто.
Также обратите внимание, что то, что программа компилируется и ссылается, не означает, что переменная, в которой отсутствует определение, не использовалась в odr.
Таким образом, это указывает на то, что обе оптимизации (-O) и LinkTimeOptimization (-flto) будут влиять на правило использования ODR?
Они не. Оптимизация это круто.