为什么在通过模板的静态调度中不需要前向声明

why no need of forward declaration in static dispatching via templates?

本文关键字:不需要 声明 静态调度 为什么      更新时间:2023-10-16

我在玩一点静态多态性,我调用一个函数,它内部调用"正确的"专化函数,这取决于初始参数的类型(基本上我在做标记)。下面是代码:

#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_type1my_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>实例化,由于两阶段查找。

编辑:我很确定这实际上是未定义的行为。如果我们看一下14.6.4.2 [temp. deep .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 parameterf_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-expressionunqualified-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_helperf<T>实例化之前不需要可见(由于两阶段名称查找)。

我相信这段代码是有效的,因为实现允许将函数模板的实例化点延迟到翻译单元的末尾,此时f_helper的定义可用。

N3936 §14.6.4.1/8 [temp.point]

函数模板、成员函数模板或类模板的成员函数或静态数据成员的专门化可以在一个翻译单元中有多个实例化点,除了上面描述的实例化点之外,对于任何在翻译单元中有实例化点的专门化,翻译单元的末尾也被认为是一个实例化点。类模板的专门化在翻译单元中最多有一个实例化点。任何模板的专门化都可能在多个翻译单元中具有实例化点。如果两个不同的实例化点赋予模板专门化不同的含义定义规则(3.2),程序是病态的,不需要诊断。