指向函数和ODR的指针

iav*_*avr 8 c++ function-pointers one-definition-rule function-templates c++11

关于ODR有很多问题,但是我找不到我要找的东西,所以如果这个副本或标题不合适就道歉.

考虑以下:

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

template<typename T>
t type() {return {type<T>};}
Run Code Online (Sandbox Code Playgroud)

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

特别地,给定一个具体的类型Tstd::string,并且假定两个不同的编译单元包括在头文件上面的代码,我想表达

type<T>().id
Run Code Online (Sandbox Code Playgroud)

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

该值是函数的地址type<T>,所以问题是一个独特的功能,type<T>程序被保证一个定义规则.iso 3.2/3说

每个程序应该只包含该程序中使用的每个非内联函数或变量的一个定义.

其中3.2/2

一个非重载函数,其名称显示为可能被评估的表达式或[...],使用的是odr,除非[...]

我假设一个函数是非内联的,如果它的地址被采用(虽然我在标准中找不到).

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

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

似乎没有一个在这里的情况.

一个可验证的示例将需要多个文件.事实上,DieterLücking给出了一个声称失败的例子,虽然它在我的情况下并没有失败(我没有采取任何形式的"保证").

那么,这是否有效?

kec*_*kec 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.
}
Run Code Online (Sandbox Code Playgroud)

文件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;    
}
Run Code Online (Sandbox Code Playgroud)

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

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