代码之家  ›  专栏  ›  技术社区  ›  vsoftco

为什么在通过模板进行静态调度时不需要转发声明?

  •  15
  • vsoftco  · 技术社区  · 11 年前

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

    #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 。取决于此内部 标签类型 ,然后调用“正确”包装器,当然这个决定是在编译时做出的。现在我真的不明白为什么这段代码有效?为什么我们不需要转发声明 f_helper ? 我之前首先定义了包装器 main (和之后 f ),我认为这是合理的,您不需要转发声明,因为编译器仅在以下情况下实例化模板 f(type1); 在中调用 main() ),在它不知道类型之前 T ,所以在实例化时编译器知道 f_wrapper .

    但正如你所见,即使我在 main() ,代码仍然有效。为什么会发生这种情况?我想这个问题有点奇怪,问代码为什么有效:)


    编辑

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

    3 回复  |  直到 11 年前
        1
  •  9
  •   Simple    11 年前

    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 parameter 这是 f_helper(类型名T::tag_type{}) . f_助手 在以下情况下不可见 f 是实例化的,但如果我们在编译完所有翻译单元后执行名称查找,就会发生这种情况。

        2
  •  4
  •   Community Mohan Dere    5 年前

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

    14.6.2/1:

    在以下形式的表达式中:

    • 后缀表达式 ( 表达式列表 [ 选择 ] )

    其中 后缀表达式 是一个 id表达式 这个 id表达式 表示 从属名称 如果 表达式列表 是依赖于类型的表达式(14.6.2.2),或者 不合格id id表达式 是一个 模板id 其中任何模板参数都依赖于模板参数。。。这些名称是未绑定的,并在模板定义的上下文和实例化点的上下文中在模板实例化点(14.6.4.1)查找。

    [ f_helper 是一个 后缀表达式 id表达式 typename T::tag_type{} 取决于类型,因此 f_助手 是从属名称。]

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

        3
  •  3
  •   Praetorian Luchian Grigore    11 年前

    呼叫 f_helper(typename T::tag_type{}); 取决于模板参数 T ,所以名字 f_helper f<T> (由于两阶段名称查找)。

    我相信代码是有效的,因为允许实现将函数模板的实例化点延迟到翻译单元结束,此时 f_助手 可用。

    编号3936 §14.6.4.1/8[温度点]

    函数模板、成员函数模板或类模板的成员函数或静态数据成员的专用化可以在翻译单元内具有多个实例化点, 对于在翻译单元内具有实例化点的任何此类专门化,翻译单元的结尾也被视为实例化点 类模板的专门化在翻译单元内最多具有一个实例化点。任何模板的专门化可以在多个翻译单元中具有实例化点。如果两个不同的实例化点给了模板专用化不同的含义 定义规则(3.2),程序格式不正确,不需要诊断。