我已经看到很多库代码,它们使用以下模式来支持C ++ 11 / C ++ 14 / C ++ 17。我有兴趣了解在“ ODR违规”和链接程序问题上,是否/为什么/在何种程度上可以。
我将参考霍华德·辛南特(Howard Hinnant)的Date库中的一个片段,该片段最近被提议进行标准化。
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
注释可能根据所使用的语言标准而有所不同)才能“起作用”,并且这种在不同的位置编译相同标头的安排语言标准是次要的技术债务,还是应该将这种情况视为错误?答案 0 :(得分:3)
ODR在这里毫无意义,因为不能保证您可以将具有不同版本C ++语言的代码编译为一个二进制文件,并使它按预期工作。
除了新的语言和库功能之外,该语言的新版本还将清理一些较旧的问题,进行一些调整以支持新功能,弃用其他功能,等等。其中某些更改可能导致代码行为发生变化,使用以前的版本时不编译,或者使用较新的版本编译未修改的代码时不编译。
这些更改在语言规范的“兼容性”附录中列出。
答案 1 :(得分:2)
ODR是在不同TU中具有相同名称的实体的强等效性的保证。每个编译器使用的等效性尚不清楚。 (我什至不知道编译器作者是否对此进行了记录。)
如果只有链接器级别,则ODR的某些变体适用于每个多语言程序(或多个C ++编译器目标文件程序)。在那个级别上,显然,实体的“名称”不是C ++限定对象名称和C ++重载的函数签名,而是杂乱无章的名称。您需要检查ABI是否达成必要的共识(如果您仅使用全局对象初始化,而在编译器之间不使用多态对象或模板,则它们不必在所有方面都达成共识。)
ODR的含义是,在任何TU中,任何使用具有外部链接的名称都是等效的。 尤其是内联或模板功能的调用确定性地执行TU中可见的功能主体中描述的操作(以功能代码固有的不确定性为重点)。 / p>
答案 2 :(得分:1)
我的$ .02:
这是否违反了ODR?
严格来说,我想是的,但是在实际应用中这可能并不重要,当然要特别提到constexpr
。
如果将一个函数声明为constexpr
,则保证编译器会生成一个编译时常量,因此就不需要生成函数体了。
对于使用C ++ 11编译的TU,所有这些TU都会满足ODR,但是我仍然希望调用函数的结果始终是编译时常量(因为显然语义没有改变,并且编译器能够在为C ++ 14进行编译时生成该常数,所以为什么不能在这里?)。
换句话说,constexpr
并没有对编译器说“将代码作为编译时常量进行编译”(无论如何,编译器将始终竭尽所能)。相反,它说:“告诉我,如果不能的话”,因此向消费者保证无论适用什么constexpr
都是它声称的那样。
ODR在这里是否没有意义,因为标准文档仅指一种语言标准,而没有指定标准之间的互操作性?
也许我们只是稍微调整一下规则。
constexpr是否会影响函数的名称处理?
作为返回值的限定符?不。
我应该期望在链接最终程序时找到此类函数的C ++ 11和C ++ 14版本,或者我希望如果它们是内联的,则链接器将选择C ++ 11或C ++ 11或C ++ 14版本。 C ++ 14版本,如果唯一的不同是constexpr批注,它们将是相同的吗?
正如我相信我们现在已经建立的那样,会没有函数体,并且人们希望两个版本生成的编译时常量是相同的。我还希望生成的代码是相同的,因为constexpr
不会为编译器自己发现的内容添加任何内容。
我应该希望这样的程序(取决于使用不同语言标准的同一个库,其中constexpr批注可能根据所使用的语言标准而有所不同)才能“起作用”,并且这种在不同语言标准下编译相同标头的安排较小的技术债务,还是应该将这种情况视为错误?
鉴于以上所述,我希望它能起作用。 -std=C++x
与编译器将接受和不接受的语法相比,是什么都重要(尽管显然有例外)。看这种方式。如果您按照描述的方式不能混合和匹配库,编译器供应商将非常不受欢迎,因此我想他们会花时间来确保可能,你可以。
那noexcept
又如何呢?好吧,如果编译器只是在整体上生成编译时常量,那么显然这些函数都无法抛出,因此我认为我们可以在这里忘记它。
如何验证本文中的假设
组合某种测试程序,并在Godbolt处检查C ++ 14和C ++ 11的代码。我无法从这里开始,因为它在平板电脑上无法使用:(