Reputation: 304172
In Eric Niebler's range-v3 library, he provides a lot of headers that each have their own global function object. They are all declared in the same way. He provides a class template static_const
:
template<typename T>
struct static_const
{
static constexpr T value {};
};
template<typename T>
constexpr T static_const<T>::value;
And then every function object of type F
is declared as:
namespace
{
constexpr auto&& f = static_const<F>::value;
}
What are the advantages of introducing the object through the static_const
template and in an unnamed namespace, as opposed to just writing:
static constexpr F f{};
Upvotes: 10
Views: 561
Reputation: 304172
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 instantiationfoo<int[4]>
. Since global const objects have internal linkage, both translation units file1.cpp and file2.cpp see separatestd::begin
objects, and the two foo instantiations will see different addresses for thestd::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>
. Sincestd::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 ofstd::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{};
Upvotes: 2