The issue is basically the one definition rule.
If you just have:
static constexpr F f{};
The name f
has internal linkage, which means that every translation unit has its own f
. The consequence of that means that, for instance, an inline function which takes the address of f
would get a different address based on which translation unit the call occurred in:
inline auto address() { return &f; } // which f??
Which means now we might actually have multiple definitions of address
. Really, any operation that takes the address of f
is suspect.
From 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
}
The code above demonstrates the potential for ODR violations if the global std::begin
function object is defined naïvely. Both functions fun in file1.cpp and main in file2.cpp cause the implicit instantiation foo<int[4]>
. Since global const objects have internal linkage, both translation units file1.cpp and file2.cpp see separate std::begin
objects, and the two foo instantiations will see different addresses for the std::begin
object. That is an ODR violation.
On the other hand, with:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
while f
still has internal linkage, static_const<F>::value
has external linkage due to its being a static data member. When we take the address of f
, it being a reference means that we're actually taking the address of static_const<F>::value
, which only has one unique address across the whole program.
An alternative is to use variable templates, which are required to have external linkage - which requires C++14, and is also demonstrated in that same link:
namespace std {
template <class T>
constexpr T __static_const{};
namespace {
constexpr auto const& begin =
__static_const<__detail::__begin_fn>;
}
}
Because of the external linkage of variable templates, every translation unit will see the same address for __static_const<__detail::__begin_fn>
. Since std::begin
is a reference to the variable template, it too will have the same address in all translation units.
The anonymous namespace is needed to keep the std::begin
reference itself from being multiply defined. So the reference has internal linkage, but the references all refer to the same object. Since every mention of std::begin
in all translation units refer to the same entity, there is no ODR violation ([basic.def.odr]/6).
In C++17, we won't have to worry about this at all with the new inline variable feature, and just write:
inline constexpr F f{};