我已经看到了许多库代码,它们使用下面的模式来支持C++11 / C++14 / C++17。我感兴趣的是,对于"ODR违规“和链接器问题,这是否/为什么/在多大程度上是可以接受的。
我将参考Hinnant的日期库中的一个片段,它是最近为标准化而提出的。
https://github.com/HowardHinnant/date/blob/master/include/date/date.h
首先,我们检查_MSC_VER和__cplusplus之类的东西,试图找出我们要获取的编译器和C++标准(在某些情况下只能粗略地完成),并将一些标记定义为constexpr关键字或空白。
#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-在两种类型的翻译单位中使用。
constexpr会影响函数的名称损坏吗?constexpr注释可能根据所使用的语言标准而有所不同)“工作”,以及按照不同的语言标准编译相同的头作为一个小的技术债务的安排,还是这种情况应该被视为一个bug?发布于 2018-06-27 22:33:59
ODR在这里是没有意义的,因为不能保证您可以将不同版本的C++语言编译成一个二进制代码,并按预期的方式工作。
除了新的语言和库特性之外,更新的语言版本还将解决一些旧的问题,进行一些调整以支持新特性,放弃其他功能等等。其中一些更改可能导致代码行为的改变,而不是在以前编译时编译,或者在用更新版本编译未经修改的代码时进行编译。
这些更改列在语言规范的“兼容性”附录中。
发布于 2018-07-06 22:07:11
ODR保证了同名实体在不同的TU (翻译单位)中的强等价性。每个编译器使用的等价性并不清楚。(我甚至不知道编译器编写人员是否将其记录在案。)
ODR的某些变体适用于每个多语言程序(或多个C++编译器目标文件程序),如果只是链接器级别的话。在那个级别上,实体的“名称”显然不是C++限定的对象名称和C++重载的函数签名,而是错误的名称。您需要检查ABI是否同意(如果您只使用全局对象初始化,编译器之间不使用多态对象或模板,则它们不必在所有方面达成一致)。
ODR的意思是,任何具有外部链接的名称的使用在任何TU中都是相当的;特别是,调用内联或模板的函数可以决定性地执行在TU中可见的函数正文中描述的操作(到函数代码固有的不确定性)。
发布于 2018-07-10 00:29:29
我的$.02:
这违反了ODR吗?
严格地说,我想,是的,但在实际的层面上,这可能并不重要,当然是特别提到constexpr。
如果函数声明为constexpr,那么编译器将生成编译时常量,因此不需要生成函数体,这就是。
对于用C++11编译的TU,ODR对于所有的TU都是满意的,但我仍然希望调用函数的结果是编译时常量(因为语义显然没有改变,编译器在为C++14编译时能够生成这个常量,那么为什么这里不能这样做呢?)
换句话说,constexpr并没有对编译器说“将此代码编译为编译时常量”(无论如何,编译器都会尽力做到这一点)。相反,它说,‘告诉我,如果你不能’,因此保证消费者的任何constexpr是适用的,这是真正的,它声称是。
难道ODR在这里毫无意义吗?因为标准文档只引用一种语言标准,并且没有指定标准之间的互操作性吗?
也许我们只是稍微扭曲了一些规则。
是否会影响函数的名称损坏?
作为返回值的限定符?不是的。
当最终程序被链接时,我应该同时找到这类函数的C++11和C++14版本,还是应该期望如果它们是内联的,则链接器将选择C++11或C++14版本,如果唯一的区别是constexpr注释,它们将是相同的?
正如我认为我们现在已经建立的,将不会有任何函数体,人们会期望由两个版本生成的编译时常数是相同的。我还希望生成的代码是相同的,因为constexpr没有为编译器自己添加任何内容。
我是否应该期望这样的程序(依赖于不同语言标准的同一个库,在这些库中,根据所使用的语言标准而有所不同)来“工作”,以及按照不同的语言标准编译相同的头作为一个小的技术债务,还是这种情况应该被看作是一个bug?
考虑到放弃,我希望它能起作用。-std=C++x更多地是关于编译器将接受和不会接受的语法,而不是任何其他语法(尽管显然有例外)。看这边。如果您不能按照描述的方式混合和匹配库,编译器供应商将非常不受欢迎,所以我想他们会花时间确保尽可能地做到这一点。
那noexcept呢?嗯,如果编译器只是全面生成编译时常量,那么显然这些函数都不能抛出,所以我认为我们可以在这里忘记它。
如何测试文章中所作的假设
编写一些测试程序,在哥德波特上检查C++14和C++11的代码。我在这里不能这样做,因为它不能在平板电脑上使用:(
https://stackoverflow.com/questions/51053820
复制相似问题