10

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{};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • The contexpr is very vital here.. If it would be a simple `const` or a non-const and you were to use such a template, you cannot guarantee initialization one after another, since ordered initialization within a TU is not done for instantiated variables. The `constexpr` guarantees that no dynamic initialization will happen, so this will not be observable here, AFAIK. – Johannes Schaub - litb Mar 12 '16 at 14:08
  • @JohannesSchaub-litb Isn't it unordered anyway? – Barry Mar 12 '16 at 14:14
  • if that were true, `int x = rand(); int y = x + 1;` at global scope would be undefined behavior :) in general for non-instantiated variables it is ordered. – Johannes Schaub - litb Mar 12 '16 at 14:17
  • 4
    Destination of `static_const` described in details in Eric Niebler's [D4381](http://ericniebler.github.io/std/wg21/D4381.html) and corresponding [article](http://ericniebler.com/2014/10/21/customization-point-design-in-c11-and-beyond/). Mostly it concerned *one-definition-rule* and preventing of its violation. – Tomilov Anatoliy Mar 12 '16 at 14:41
  • 6
    This is basically poor man's inline variables. – T.C. Mar 12 '16 at 17:42
  • @T.C. I'm not sure what that means. – Barry Mar 12 '16 at 18:00
  • 3
    @Barry See [N4424](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4424.pdf), a proposal introduce inline variables mostly to solve ODR problems. – Morwenn Mar 12 '16 at 23:12
  • 1
    @Orient, that should be an answer (as it's the right one). – Jonathan Wakely Jul 22 '16 at 10:33

1 Answers1

2

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{};
Barry
  • 286,269
  • 29
  • 621
  • 977