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{};
4

1 回答 1

2

这个问题基本上是一个定义规则。

如果你只有:

static constexpr F f{};

该名称f具有内部链接,这意味着每个翻译单元都有自己的f. 其结果意味着,例如,获取地址的内联函数f将根据调用发生在哪个翻译单元中获得不同的地址:

inline auto address() { return &f; } // which f??

这意味着现在我们实际上可能有多个address. 确实,任何获取地址的操作f都是可疑的。

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
}

std::begin上面的代码演示了如果全局函数对象被天真地定义,可能会违反 ODR 。file1.cpp 中的 fun 函数和 file2.cpp 中的 main 函数都会导致隐式实例化foo<int[4]>。由于全局 const 对象具有内部链接,因此翻译单元 file1.cpp 和 file2.cpp 都看到单独std::begin的对象,并且两个 foo 实例化将看到std::begin对象的不同地址。这是违反 ODR 的。


另一方面,与:

namespace
{
    constexpr auto&& f = static_const<F>::value;
}

虽然f仍然具有内部链接,但由于它是静态数据成员而static_const<F>::value具有外部链接。当我们获取 的地址时f,它是一个引用意味着我们实际上正在获取 的地址static_const<F>::value,它在整个程序中只有一个唯一的地址。

另一种方法是使用变量模板,它需要具有外部链接 - 这需要 C++14,并且也在同一个链接中进行了演示:

namespace std {
  template <class T>
  constexpr T __static_const{};
  namespace {
    constexpr auto const& begin =
      __static_const<__detail::__begin_fn>;
  }
}

由于变量模板的外部链接,每个翻译单元将看到相同的地址__static_const<__detail::__begin_fn>。由于std::begin是对变量模板的引用,因此它在所有翻译单元中也将具有相同的地址。

需要匿名命名空间来防止std::begin引用本身被多次定义。所以引用具有内部链接,但引用都引用同一个对象。由于std::begin所有翻译单元中的每次提及都指的是同一个实体,因此不存在 ODR 违规([basic.def.odr]/6)。


在 C++17 中,使用新的内联变量功能,我们完全不必担心这一点,只需编写:

inline constexpr F f{};
于 2018-09-06T19:43:05.923 回答