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{};
The clean, reliable way to declare and define global variables is to use a header file to contain an extern declaration of the variable. The header is included by the one source file that defines the variable and by all the source files that reference the variable.
You need to do: int * const ptr; to make a constant pointer, so that the rule will apply to it. Also note that this is one reason I prefer to consistently put const after the type: int const instead of const int .
All functions, constants, and types that are intended to be used outside the file should be declared in a header file. Static functions, file-local constants and types should be declared at the top of the . c file.
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{};
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With