В библиотеке 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{};
Проблема в основном в одном правиле определения.
Если у вас просто есть:
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{};
Других решений пока нет …