【问题标题】:constexpr global of class type类类型的 constexpr 全局
【发布时间】:2013-12-20 14:58:50
【问题描述】:

我的理解是 constexpr 类类型的全局变量几乎无法使用,因为

  • 这样的对象必须在每个 TU 中定义,因为constexpr 不允许前向声明对象。

  • 默认链接为static 将导致在内联函数中命名对象(ODR 使用与否)违反 ODR,因为各自的 inline 定义将具有不同的含义。

  • 声明为extern constexpr,每个TU 有一个定义,如果对象是ODR 使用的,则会违反ODR 规则,当引用它时会发生这种情况。

    • 对隐式 this 参数进行引用,即使成员函数未使用它。
    • 如果您尝试通过引用传递对象,显然会发生这种情况。
    • 如果您尝试按值传递对象也会发生这种情况,这会隐式使用复制或移动构造函数,根据定义,该构造函数通过引用传递。
    • 如果一个对象被声明为extern constexpr,即使没有使用 ODR,GCC 和 Clang 都会抱怨 ODR 违规(多个定义)。

这一切都正确吗?有没有办法让 constexpr 全局类类型不包装在 inline 函数中?

【问题讨论】:

  • 我相信你的第二点是错误的。使用常量的 value 并不会直接使用它。实际上,该标准使用具有内部链接的constexpr 变量(参见std::allocator_arg 作为示例)。 ODR 不关心您在函数中为常量表达式使用什么 names
  • @Simple 该函数将违反 ODR,而不是对象。两个 TU 中包含的同一标头中的 inline 函数的两个定义必须满足 ODR 规则,其中一部分是每个名称必须引用同一个实体。 std::allocator_arg 可能会让用户陷入困境,但从 inline 函数打印其地址很容易暴露 UB。
  • 如果您需要它的地址,那么显然您违反了 ODR,但您在使用非类类型时遇到了同样的问题。请参阅 3.2/2:名称显示为潜在求值表达式的变量是 odr-used,除非它是满足出现在常量表达式 (5.19) 中的要求和左值到右值转换 ( 4.1) 立即应用.
  • @Simple 查看子项目符号列表,了解悄悄获取地址的内容。这就是我为这个问题指定班级类型的原因;与非类类型不同,类类型的左值到右值转换意味着构造函数使用 ODR 并否定转义子句。
  • 我不认为你关于this 的观点是正确的。参见 5.19/2 [expr.const]: this (5.1) 除非它作为后缀表达式出现在类成员访问表达式中,包括非静态成员函数体中隐式转换的结果 和:为文字类或 constexpr 函数调用除 constexpr 构造函数以外的函数。由于函数调用替换,通过引用传递给 constexpr 函数也不会使用它。不久前我有一个关于这一点的问题。

标签: c++ c++11 global constexpr one-definition-rule


【解决方案1】:

全局 constexpr 变量可以使用一些宏魔术和众所周知的额外间接级别在头文件中安全地定义 ODR

#define PP_GLOBAL_CONSTEXPR_VARIABLE(type, var, value)                   \
namespace var##detail {                                                  \
template<class = void>                                                   \
struct wrapper                                                           \
{                                                                        \
     static constexpr type var = value;                                  \
};                                                                       \
template<class T>                                                        \
constexpr type wrapper<T>::var;                                          \
}                                                                        \
namespace {                                                              \
auto const& var = var##detail::wrapper<>::var;                           \
}

宏在未命名的命名空间内提供引用 到实现类模板中的对象实例。

标头内未命名命名空间中的每个对象都会生成 每个翻译单元中包含其标题的唯一实例。 此外,为了防止 ODR 违规,重要的是对象 在例如一个函数模板的多个实例化是相同的。

但是,对于引用,它们具有不同的身份并不重要; 只要它们在实现中引用相同的对象实例 类模板。

您可以将此宏包装在标头中,并将其安全地包含在许多 TU 中而不会出现问题。

有关详细信息,请参阅 Boost 邮件列表中的以下讨论: http://lists.boost.org/Archives/boost/2007/06/123380.php

【讨论】:

  • 啊,模板的静态成员合并了!为什么引用 constexpr 也不是?为什么不使用template&lt; class = void &gt; 并去掉占位符?
  • @Potatoswatter 我不认为你可以写auto constexpr&amp;,无论如何,有什么区别?
  • 因为const 只为整数类型创建(核心)常量表达式。我不确定通过引用访问constexpr 对象会产生constexpr 语义,尽管情况应该如此。 constexpr auto const &amp; 将是语法,尽管这可能会非法限定引用。
  • @Potatoswatter placeholder 是我为自己的实用工具箱改编的 Boost 代码的残余。我认为您是正确的,可以使用默认的void 模板参数将其删除。
  • @Potatoswatter 宏不适合你吗?还是有其他原因不再接受答案?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-02
  • 2015-12-03
  • 2022-01-09
  • 1970-01-01
  • 2014-08-22
相关资源
最近更新 更多