Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Declaring global const objects in a header file

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{};
like image 223
Barry Avatar asked Mar 12 '16 13:03

Barry


People also ask

Can you declare global variables in a header file?

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.

How do you declare a constant in a header file?

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 .

Should constants be defined in header files?

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.


1 Answers

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{};
like image 78
Barry Avatar answered Oct 07 '22 17:10

Barry