15

我正在玩一些静态多态性,我正在调用一个函数,该函数在内部根据初始参数的类型调用“正确的”专用函数(基本上我正在做标记)。这是代码:

#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_type1my_type2在内部必须tag_type使用适当的标签tag1/进行 typedef 调用tag2。根据这个 internal tag_type,然后调用“正确的”包装器,这个决定当然是在编译时做出的。现在我真的不明白为什么这段代码有效?为什么我们不需要前向声明f_helper?我首先在之前main(和之后f)定义了包装器,虽然我还可以,这是有道理的,你不需要转发声明,因为编译器仅在f(type1);调用(in main())时才实例化模板,然后它不知道类型T,所以在实例化的时候编译器就知道了f_wrapper

但是如您所见,即使我声明了包装器 AFTER main(),代码仍然有效。为什么会这样?我想这个问题有点奇怪,问为什么代码有效:)


编辑

即使在 gcc5 和 gcc HEAD 6.0.0 中,代码也会继续编译。

4

3 回答 3

9

f_helper(typename T::tag_type{})是一个依赖类型的表达式,因为T::tag_type是一个依赖类型。这意味着由于两阶段查找,在实例化f_helper之前不需要可见。f<T>

编辑:我很确定这实际上是未定义的行为。如果我们查看 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 parameterf_helper(typename T::tag_type{})f_helper实例化时不可见f,但如果我们在编译所有翻译单元后执行名称查找,就会出现这种情况。

于 2014-08-07T17:21:26.680 回答
4

我同意,代码格式不正确。我很惊讶 g++ 和 clang++ 都没有对此发出警告。

14.6.2/1:

在以下形式的表达式中:

  • 后缀表达式 ( 表达式列表[ opt ])

其中postfix-expressionid-expression,如果expression-list中的任何表达式是依赖于类型的表达式 (14.6.2.2) 或如果id-的unqualified-id,则id-expression表示依赖名称expression是一个模板 ID,其中任何模板参数都依赖于模板参数。...此类名称是未绑定的,并且在模板定义的上下文和实例化点的上下文中都在模板实例化点 (14.6.4.1) 处查找。

[f_helperpostfix-expressionid-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) 的查找部分,只能找到在模板定义上下文或模板实例化上下文中找到的函数声明。

于 2014-08-07T17:29:54.523 回答
3

对 的调用f_helper(typename T::tag_type{});取决于模板参数T,因此名称f_helper在实例化点之前不需要可见f<T>(由于两阶段名称查找)。

我相信代码可以工作,因为允许实现将函数模板的实例化点延迟到翻译单元结束时,此时定义f_helper可用。

N3936 §14.6.4.1/8 [温度点]

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

于 2014-08-07T18:08:35.793 回答