8

关于 ODR 有很多问题,但我找不到我要找的东西,所以如果这是重复的或标题不合适,我们深表歉意。

考虑以下:

struct t {t(*id)();};

template<typename T>
t type() {return {type<T>};}

这是对我为每种类型定义唯一标识符的尝试的过度简化,希望它在不同的编译单元中保持唯一。

特别是,给定一个具体的类型Tstd::string假设两个不同的编译单元在头文件中包含上述代码,我想表达

type<T>().id

在两个单元中采用相同的值(类型t(*)()),因此用作类型的唯一标识符T

该值是函数的地址type<T>,所以问题是程序type<T>中的唯一函数是否由单定义规则保证。iso 3.2/3 说

每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的准确定义。

3.2/2 在哪里

名称显示为潜在求值表达式或 [...] 的非重载函数是 odr 使用的,除非 [...]

如果一个函数的地址被占用,我假设它是非内联的(尽管我在标准中找不到它)。

iso 3.2/5 列出了一些例外,但对函数的唯一引用是

具有外部链接的内联函数、[...]、非静态函数模板、[...]、类模板的成员函数或未指定某些模板参数的模板特化 [...]

而且这里似乎没有。

一个可验证的示例需要多个文件。事实上,Dieter Lücking给出了一个声称失败的例子,尽管在我的情况下它并没有失败(我不认为这是任何形式的“保证”)。

那么,这会奏效吗?

4

1 回答 1

4

所以 3.2/5 实际上看起来是相当强大的支持。首先请注意,定义是源代码构造,而不是目标代码构造,尽管显然存在非常密切的关系。3.2/5 是说可以有多个非静态函数模板的定义,而且在这种情况下,它必须表现得好像只有一个定义一样。如果一个函数在不同的翻译单元中有不同的地址,那么至少在我的阅读中,这并不是只有一个定义。

尤其如此,因为函数指针可以作为非类型模板参数传递。此类参数必须是常量,并且对于所有翻译单元都必须相同。例如,字符串文字不能精确地成为这样的参数,因为它的地址因翻译单元而异。

是否满足所有要求将完全取决于多个定义的上下文,因为它们处理诸如名称解析等问题。但是,它们都是“普通”的要求,属于“当然”类型。例如,违反它会是这样的:

文件1.cpp

static int i;

// This is your template.
template <typename T>
void foo() {
    i; // Matches the above i.
}

文件2.cpp

static int i;

// This is your template. You are normally allowed to have multiple
// identical definitions of it.
template <typename T>
void foo() {
    // Oops, matches a different entity. You didn't satisfy the requirements.
    // All bets are off.
    i;    
}

我知道 Linux 通过弱符号支持多个定义。事实上,在 Linux 上,幸运的例子并没有因此而失败。我对他要求平台的回答发表了评论。在链接时,链接器将丢弃除一个之外的所有弱符号实例。显然,如果实例实际上并不相同,那将很糟糕。但是 3.2/5 中的这些要求旨在确保实例实际上都是相同的,因此链接器只能保留一个。

附录:Dieter Lucking 现在说他有一个编译问题,事实上他并没有失败。不过,如果熟悉 Windows DLL 内部的人可以在这里评论 Visual Studio 如何处理这个问题,那就太好了。

于 2014-05-03T12:59:26.123 回答