为什么在通过模板的静态调度中不需要前向声明
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;
}
因此,使用参数my_type1
或my_type2
调用f(T)
,内部必须使用适当的标签tag1
/tag2
定义tag_type
类型。根据这个内部tag_type
,然后调用"正确的"包装器,当然这个决定是在编译时做出的。现在我真的不明白为什么这个代码是工作?为什么我们不需要向前申报f_helper
?我首先在main
之前定义了包装器(在f
之后),我认为好吧,这是有意义的,你不需要转发声明,因为编译器只在f(type1);
被调用时实例化模板(在main()
中),在它不知道类型T
之前,所以在实例化时编译器知道f_wrapper
。
但是正如您所看到的,即使我在main()
之后声明包装器,代码仍然可以工作。为什么会发生这种情况?我想这个问题有点奇怪,问为什么代码工作:)
编辑
代码甚至在gcc5和gcc HEAD 6.0.0中继续编译。
f_helper(typename T::tag_type{})
是类型依赖表达式,因为T::tag_type
是依赖类型。这意味着f_helper
不需要是可见的,直到f<T>
实例化,由于两阶段查找。
对于依赖模板形参的函数调用候选函数使用通常的查找规则(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
是f_helper(typename T::tag_type{})
。当f
实例化时,f_helper
是不可见的,但如果我们在编译完所有翻译单元后执行名称查找,它将是可见的。
我同意,代码是错误的。我很惊讶g++和clang++都没有对此发出警告。
14.6.2/1:
在形式为
的表达式中
- postfix-expression
(
expression-list [opt])
中后缀表达式是id-expression,如果表达式列表中的任何表达式是类型依赖表达式(14.6.2.2),或者id-expression的unqualified-id是模板id,其中任何模板参数依赖于模板形参. ...,则id-expression表示依赖于的名称这样的名称是不绑定的,并在模板定义的上下文中和实例化点的上下文中(14.6.4.1)查找。
[f_helper
是一个后缀表达式和id-表达式, 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{});
的调用依赖于模板参数T
,因此名称f_helper
在f<T>
实例化之前不需要可见(由于两阶段名称查找)。
我相信这段代码是有效的,因为实现允许将函数模板的实例化点延迟到翻译单元的末尾,此时f_helper
的定义可用。
N3936 §14.6.4.1/8 [temp.point]
函数模板、成员函数模板或类模板的成员函数或静态数据成员的专门化可以在一个翻译单元中有多个实例化点,除了上面描述的实例化点之外,对于任何在翻译单元中有实例化点的专门化,翻译单元的末尾也被认为是一个实例化点。类模板的专门化在翻译单元中最多有一个实例化点。任何模板的专门化都可能在多个翻译单元中具有实例化点。如果两个不同的实例化点赋予模板专门化不同的含义定义规则(3.2),程序是病态的,不需要诊断。
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 为什么转换函数声明不需要至少一个定义类型说明符
- 根据需要声明资源,而不重复它们
- 如果在C++中不需要构造函数或析构函数,是否有必要显式声明它?
- C++不正确,不需要重新声明类成员变量 MFC 手工解决方案/项目 MS VS 2015
- 为什么 c++ lambda 捕获不需要类型声明?
- C++,为什么结构/类中不需要前向声明
- C++中的函数声明后不需要分号(';')吗?
- 下面链接中的声明不需要存储类说明符"static"。我说的对吗?
- 为什么C++需要通过标头或语句进行前向声明,而 Java 不需要?
- 为什么我需要声明指针而不是对象
- 朋友类概念不需要声明
- 模板类和处理不需要的类型声明的良好实践
- 为什么在通过模板的静态调度中不需要前向声明
- 创建多个对象而不需要多个声明
- 使用函子的返回类型来声明模板方法的返回类型,不需要decltype
- C++ getline() 不需要命名空间声明
- c++调用约定是否受标准约束,因为在声明fn时不需要定义函数的返回类型
- 为什么C++不需要班级成员的前向声明?
- 语法错误:需要声明.为什么gsoap不能读取vector