在Eric Niebler的range-v3库中,他提供了许多标题,每个标题都有自己的全局函数对象。他们都以同样的方式宣布。他提供了一个课程模板static_const
:
F
然后,类型namespace
{
constexpr auto&& f = static_const<F>::value;
}
的每个函数对象都声明为:
static_const
通过static constexpr F f{};
模板和在未命名的命名空间中引入对象有什么好处,而不是只写:
int()
答案 0 :(得分:0)
问题基本上是一个定义规则。
如果您只有:
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
具有 external 链接,因为它是静态数据成员。当我们采用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{};