[英]why no need of forward declaration in static dispatching via templates?
我正在玩静态多态,我正在调用一个函数,它根据初始参数的类型在内部调用“正确”的专用函数(基本上我正在做标记)。 这是代码:
#include <iostream>
using namespace std;
// tags
struct tag1{};
struct tag2{};
// the compliant types, all should typedef tag_type
struct my_type1
{
using tag_type = tag1;
};
struct my_type2
{
using tag_type = tag2;
};
// static dispatch via tagging
template <typename T>
void f(T)
{
cout << "In void f<typename T>(T)" << endl;
// why can I call f_helper without forward definition?!?
f_helper(typename T::tag_type{});
}
int main()
{
my_type1 type1;
my_type2 type2;
// how does f below knows about f_helper ?!?!
// even after instantiation f_helper shouldn't be visible!
f(type1);
f(type2);
}
// helper functions
void f_helper(tag1)
{
cout << "f called with my_type1" << endl;
}
void f_helper(tag2)
{
cout << "f called with my_type2" << endl;
}
所以, f(T)
是带一个参数my_type1
或my_type2
在内部必须的typedef tag_type
与适当的标签tag1
/ tag2
。 根据这个内部tag_type
,然后调用“right”包装器,这个决定当然是在编译时做出的。 现在我真的不明白为什么这个代码工作? 为什么我们不需要前向声明f_helper
? 我首先在main
(和f
之后)之前定义了包装器,我认为这是有道理的,你不需要转发声明,因为编译器仅在f(type1);
时实例化模板f(type1);
在main()
不知道类型T
)之前调用(在main()
),因此在实例化时编译器知道f_wrapper
。
但正如你所看到的,即使我声明了包装器AFTER main()
,代码仍然有效。 为什么会这样? 我想问题有点奇怪,问为什么代码有效:)
编辑
即使在gcc5和gcc HEAD 6.0.0中,代码仍继续编译。
f_helper(typename T::tag_type{})
是一个依赖于类型的表达式,因为T::tag_type
是一个依赖类型。 这意味着在由于两阶段查找而实例化f<T>
之前, f_helper
不需要是可见的。
编辑:我很确定这实际上是未定义的行为。 如果我们看一下14.6.4.2 [temp.dep.candidate],我们会看到这段话:
对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1,3.4.2,3.4.3)找到候选函数,除了:
- 对于使用非限定名称查找(3.4.1)或限定名称查找(3.4.3)的查找部分,仅找到模板定义上下文中的函数声明。
- 对于使用关联命名空间(3.4.2)的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。
如果函数名称是非限定id并且调用将是格式错误或者找到更好的匹配,则相关命名空间内的查找会考虑所有在所有翻译单元中的那些名称空间中引入外部链接的函数声明,而不仅仅是考虑在模板定义和模板实例化上下文中找到的那些声明,则程序具有未定义的行为。
我的最后一段表明这是未定义的行为。 这里function call that depends on a template parameter
的function call that depends on a template parameter
是f_helper(typename T::tag_type{})
。 当f
被实例化时, f_helper
不可见,但是如果我们在编译f_helper
所有翻译单元后执行了名称查找。
我同意,代码是不正确的。 我很惊讶g ++和clang ++都没有对此发出警告。
14.6.2 / 1:
在表达式中:
- postfix-expression
(
表达式列表 [ opt ])
其中postfix-expression是一个id-expression ,如果expression-list中的任何表达式是依赖于类型的表达式(14.6.2.2),或者id-的非限定id ,则id-expression表示一个从属名称。 expression是一个template-id ,其中任何模板参数都依赖于模板参数。 ...这些名称是未绑定的,并且在模板定义的上下文和实例化的上下文中都被查找模板实例化(14.6.4.1)。
[ f_helper
是一个postfix-expression和id-expression , typename T::tag_type{}
依赖于类型,因此f_helper
是一个依赖名称。]
14.6.4 / 1:
在解析依赖名称时,会考虑以下来源的名称:
在模板定义点可见的声明。
来自名称空间的声明与实例化上下文(14.6.4.1)和定义上下文中的函数参数的类型相关联。
14.6.4.1/6:
依赖于模板参数的表达式的实例化上下文是在同一翻译单元中模板特化的实例化之前声明的具有外部链接的声明集。
14.6.4.2/1:
对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1,3.4.2,3.4.3)找到候选函数,除了:
对于使用非限定名称查找(3.4.1)或限定名称查找(3.4.3)的查找部分,仅找到模板定义上下文中的函数声明。
对于使用关联命名空间(3.4.2)的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明。
对f_helper(typename T::tag_type{});
的调用f_helper(typename T::tag_type{});
取决于模板参数T
,因此在f<T>
的实例化之前,名称f_helper
不需要可见(由于两个阶段名称查找)。
我相信代码是有效的,因为允许实现延迟功能模板的实例化,直到翻译单元结束,此时f_helper
的定义可用。
N3936§14.6.4.1 / 8 [temp.point]
函数模板,成员函数模板或类模板的成员函数或静态数据成员的特化可以在翻译单元内具有多个实例化点,并且除了上述实例化的点之外, 对于任何这样的实例化。 在翻译单元内具有实例化点的专门化,翻译单元的末尾也被认为是实例化的点 。 类模板的专门化在翻译单元中最多只有一个实例化点。 任何模板的特化可以在多个翻译单元中具有实例化点。 如果两个不同的实例化点根据一个定义规则(3.2)给出模板特化不同的含义,则该程序是不正确的,不需要诊断。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.