Объявление глобальных const-объектов в заголовочном файле

В библиотеке range-v3 Эрика Ниблера он предоставляет множество заголовков, каждый из которых имеет свой собственный объект глобальной функции. Все они объявлены одинаково. Он предоставляет шаблон класса static_const:

template<typename T>
struct static_const
{
static constexpr T value {};
};

template<typename T>
constexpr T static_const<T>::value;

И тогда каждый объект функции типа F объявлен как:

namespace
{
constexpr auto&& f = static_const<F>::value;
}

Каковы преимущества введения объекта через static_const шаблон а также в безымянном пространстве имен, а не просто писать:

static constexpr F f{};

9

Решение

Проблема в основном в одном правиле определения.

Если у вас просто есть:

static constexpr F f{};

Имя f имеет внутреннюю связь, что означает, что каждая единица перевода имеет свой собственный f, Следствие этого означает, что, например, встроенная функция, которая принимает адрес f получит другой адрес в зависимости от того, в каком блоке перевода произошел вызов:

inline auto address() { return &f; } // which f??

Что означает, что теперь мы можем иметь несколько определений address, Действительно, любая операция, которая принимает адрес f Подозреваемый

От D4381:

// <iterator>
namespace std {
// ... define __detail::__begin_fn as before...
constexpr __detail::_begin_fn {};
}

// header.h
#include <iterator>
template <class RangeLike>
void foo( RangeLike & rng ) {
auto * pbegin = &std::begin; // ODR violation here
auto it = (*pbegin)(rng);
}

// file1.cpp
#include "header.h"void fun() {
int rgi[] = {1,2,3,4};
foo(rgi); // INSTANTIATION 1
}

// file2.cpp
#include "header.h"int main() {
int rgi[] = {1,2,3,4};
foo(rgi); // INSTANTIATION 2
}

Приведенный выше код демонстрирует возможность нарушений ODR, если глобальный std::begin Функциональный объект определяется наивно. Обе функции fun в file1.cpp и main в file2.cpp вызывают неявную реализацию foo<int[4]>, Поскольку глобальные const-объекты имеют внутреннюю связь, обе единицы перевода file1.cpp и file2.cpp см. Отдельно std::begin объекты, и два экземпляра Foo будут видеть разные адреса для std::begin объект. Это нарушение ODR.


С другой стороны, с:

namespace
{
constexpr auto&& f = static_const<F>::value;
}

в то время как f все еще имеет внутреннюю связь, static_const<F>::value имеет внешний связь из-за того, что он является статическим членом данных. Когда мы берем адрес fэто ссылка означает, что мы на самом деле принимаем адрес static_const<F>::value, который имеет только один уникальный адрес во всей программе.

Альтернативой является использование шаблонов переменных, которые должны иметь внешнюю связь — что требует C ++ 14, и также показано в той же ссылке:

namespace std {
template <class T>
constexpr T __static_const{};
namespace {
constexpr auto const& begin =
__static_const<__detail::__begin_fn>;
}
}

Из-за внешней связи переменных шаблонов каждый блок перевода будет видеть один и тот же адрес для __static_const<__detail::__begin_fn>, поскольку std::begin является ссылкой на шаблон переменной, он также будет иметь одинаковый адрес во всех единицах перевода.

Анонимное пространство имен необходимо для std::begin ссылаться на себя из множества определений. Таким образом, ссылка имеет внутреннюю связь, но все ссылки ссылаются на один и тот же объект. Поскольку каждое упоминание о std::begin во всех единицах перевода ссылаются на одну и ту же сущность, нарушения ODR нет ([Basic.def.odr] / 6).


В C ++ 17 нам вообще не придется об этом беспокоиться с новой функцией встроенных переменных, а просто пишем:

inline constexpr F f{};
0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]