【问题标题】:C++ Code with conditional constexpr annotations vs. ODR and linker带有条件 constexpr 注释的 C++ 代码与 ODR 和链接器
【发布时间】:2018-06-27 02:42:25
【问题描述】:

我看到很多库代码使用以下模式来支持 C++11/C++14/C++17。我有兴趣了解“ODR 违规”和链接器问题是否/为什么/在多大程度上是可以的。

我将参考 Howard Hinnant 的 Date 库中的一个 sn-p,它最近被提议用于标准化。

https://github.com/HowardHinnant/date/blob/master/include/date/date.h

首先,我们检查_MSC_VER__cplusplus 之类的内容,以尝试找出我们所针对的C++ 编译器和标准(在某些情况下只能粗略地完成),并将一些标记定义为@987654324 @关键字,或空白。

#if defined(_MSC_VER) && (!defined(__clang__) || (_MSC_VER < 1910))
// MSVC
#  if _MSC_VER < 1910
//   before VS2017
#    define CONSTDATA const
#    define CONSTCD11
#    define CONSTCD14
#    define NOEXCEPT _NOEXCEPT
#  else
//   VS2017 and later
#    define CONSTDATA constexpr const
#    define CONSTCD11 constexpr
#    define CONSTCD14 constexpr
#    define NOEXCEPT noexcept
#  endif

#elif defined(__SUNPRO_CC) && __SUNPRO_CC <= 0x5150
// Oracle Developer Studio 12.6 and earlier
#  define CONSTDATA constexpr const
#  define CONSTCD11 constexpr
#  define CONSTCD14
#  define NOEXCEPT noexcept

#elif __cplusplus >= 201402
// C++14
#  define CONSTDATA constexpr const
#  define CONSTCD11 constexpr
#  define CONSTCD14 constexpr
#  define NOEXCEPT noexcept
#else
// C++11
#  define CONSTDATA constexpr const
#  define CONSTCD11 constexpr
#  define CONSTCD14
#  define NOEXCEPT noexcept
#endif

然后,许多成员函数等都使用这些宏进行注释:

// date composition operators

CONSTCD11 year_month operator/(const year& y, const month& m) NOEXCEPT;
CONSTCD11 year_month operator/(const year& y, int          m) NOEXCEPT;

CONSTCD11 month_day operator/(const day& d, const month& m) NOEXCEPT;
CONSTCD11 month_day operator/(const day& d, int          m) NOEXCEPT;
CONSTCD11 month_day operator/(const month& m, const day& d) NOEXCEPT;
CONSTCD11 month_day operator/(const month& m, int        d) NOEXCEPT;
CONSTCD11 month_day operator/(int          m, const day& d) NOEXCEPT;

CONSTCD11 month_day_last operator/(const month& m, last_spec) NOEXCEPT;
CONSTCD11 month_day_last operator/(int          m, last_spec) NOEXCEPT;
CONSTCD11 month_day_last operator/(last_spec, const month& m) NOEXCEPT;
CONSTCD11 month_day_last operator/(last_spec, int          m) NOEXCEPT;

现在,假设我有一个程序,其中包含一些以 C++11 标准编译的库和一些以 C++14 标准编译的库,其中许多都包含此文件并使用这些函数。

这意味着,标记为CONSTCD14 的函数将在C++14 翻译单元中标记为constexpr,而在C++11 编译单元中将不会标记为constexpr。让我们假设这些函数在两种翻译单元中都使用了 ODR。

  • 这是否违反 ODR?
  • 这里的 ODR 是不是没有意义,因为标准文档只涉及单一语言标准,并没有指定标准之间的互操作性?
  • constexpr 会影响函数的名称修改吗?
  • 我应该期望在链接最终程序时找到此类函数的 C++11 和 C++14 版本,还是期望如果它们是内联的,则链接器将选择 C++11 或C++14 版本,如果唯一的区别是 constexpr 注释,它们将是相同的吗?
  • 我是否应该期望这样的程序(取决于不同语言标准的同一库,其中constexpr 注释可能因所使用的语言标准而异)“工作”,以及这种以不同语言标准编译相同头文件的安排作为轻微的技术债务,还是应该将这种情况视为错误?

【问题讨论】:

    标签: c++ c++11 c++14 language-lawyer one-definition-rule


    【解决方案1】:

    ODR 在这里毫无意义,因为无法保证您可以将具有不同 C++ 语言版本的代码编译成一个二进制文件并使其按预期工作。

    除了新的语言和库功能之外,新版本的语言将清理一些旧问题,进行一些调整以支持新功能,弃用其他内容等。其中一些更改可能会导致代码行为发生变化,以前编译时不编译,或者用较新版本编译未修改的代码时不编译。

    这些变化列在语言规范的“兼容性”附录中。

    【讨论】:

      【解决方案2】:

      ODR 是不同TU(翻译单元)中同名实体强等价的保证。每个编译器使用的等效性尚不清楚。 (我什至不知道编译器作者是否记录了它。)

      ODR 的某些变体适用于每个多语言程序(或多个 C++ 编译器目标文件程序),如果只是链接器级别的话。在那个级别上,实体的“名称”显然不是 C++ 限定的对象名称和 C++ 重载的函数签名,而是损坏的名称。您可以根据需要检查 ABI 是否一致(如果您只使用全局对象初始化并且在编译器之间没有多态对象或模板,则它们不必在所有事情上都达成一致)。

      ODR 的含义是任何使用带有外部链接的名称在任何 TU 中都在某种程度上是等效的; 特别是内联或模板函数的调用确定性地执行在 TU 中可见的函数体中描述的操作(到函数代码固有的非确定性)。

      【讨论】:

        【解决方案3】:

        我的 $.02:

        这是否违反了 ODR?

        严格来说,我想是的,但在实际层面上这可能并不重要,当然具体参考constexpr

        如果一个函数被声明为constexpr,那么编译器会保证生成一个编译时常量,因此不需要生成函数体,仅此而已。

        对于使用 C++11 编译的 TU,所有这些 TU 都将满足 ODR,但我仍然希望调用函数的结果无论如何都是编译时常量(因为显然语义没有改变,而且编译器在为 C++14 编译时能够生成该常量,为什么这里不能呢?)。

        换句话说,constexpr 并没有对编译器说“将此代码编译为编译时常量”(无论如何,编译器都会尽其所能)。相反,它说,“如果你不能告诉我”,因此向消费者保证无论 constexpr 应用于什么,它确实是它声称的那样。

        这里的 ODR 是不是没有意义,因为标准文档只指单一语言标准,并没有规定标准之间的互操作性?

        也许我们只是稍微改变了规则。

        constexpr 会影响函数的名称修改吗?

        作为返回值的限定符?没有。

        我应该期望在链接最终程序时找到此类函数的 C++11 和 C++14 版本,还是期望如果它们是内联的,链接器将选择 C++11 或C++14 版本,如果唯一的区别是 constexpr 注释,它们将是相同的?

        我相信我们现在已经确定,将没有函数体,并且人们会期望两个版本生成的编译时常量是相同的。我还希望生成的代码是相同的,因为constexpr 不会在编译器已经为自己找到的内容中添加任何内容。

        我是否应该期望这样的程序(取决于不同语言标准的同一库,其中 constexpr 注释可能会根据所使用的语言标准而有所不同)“工作”,并且这种以不同语言标准编译相同头文件的安排作为轻微的技术债务,还是应该将这种情况视为错误?

        鉴于上述情况,我希望它能够工作。 -std=C++x 更多的是关于编译器将接受和不接受的语法而不是其他任何语法(尽管显然有例外)。看看这个样子。如果您无法按照您描述的方式混合和匹配库,编译器供应商将非常不受欢迎,所以我想他们会花时间确保可以,可以。

        那么noexcept 呢。好吧,如果编译器只是全面生成编译时常量,那么显然这些函数都不会抛出,所以我认为我们可以在这里忘记它。

        如何检验本文中的假设

        编写某种测试程序并检查Godbolt 处的代码是否适用于 C++14 和 C++11。我不能从这里这样做,因为它在平板电脑上无法使用:(

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-29
          • 2013-12-31
          • 2014-07-29
          • 2019-08-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多