c++ - 模板定义如何与模板声明匹配?

标签 c++ templates

模板声明与模板定义的匹配程度如何?如果“他们的模板名称 [...] 引用相同的模板和 [...]”(14.4 [temp.type] p1),我在标准中发现了一些关于模板 ID 引用相同函数的文本,但我找不到模板名称的定义或模板名称引用同一个模板时。我不确定我是否在正确的轨道上,因为我还没有很好地破译语法来判断模板 ID 是模板定义/声明的一部分,还是只是模板的使用。

例如,以下程序运行良好。

#include <iostream>

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

template<typename T>
T foo(T t)
{ std::cout << "A\n"; return 0; }

如果我改变在模板定义中使用模板参数的方式,名称显然不再引用同一个模板,并且链接失败。
#include <iostream>

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }

// or

template<typename T>
struct identity {
    typedef T type;
};

template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }

接下来,如果我将模板定义移动到另一个翻译单元,对于我的 C++ (MSVC 11 beta) 实现,无论我如何说类型,程序都可以运行。
//main.cpp

template<typename T>
T foo(T t);

int main() {
    foo(1);
}

//definition.cpp
#include <iostream>

template<typename T>
struct identity {
    typedef T type;
};

template<typename T>
typename identity<T>::type
foo(T t) { std::cout << "A\n"; return 0; }

template int foo<int>(int);

或者
//definition.cpp
#include <iostream>

template<typename T>
int foo(T t) { std::cout << "A\n"; return 0; }

template int foo<int>(int);

或者即使定义根本不是模板:
//definition.cpp
#include <iostream>

int foo(T t) { std::cout << "A\n"; return 0; }

显然链接是成功的,因为无论为创建符号而实例化的模板如何,签名/错位名称都是相同的。我认为这种未定义的行为是因为我违反了:

§ 14.1 [temp] p6

A function template, member function of a class template, or static data member of a class template shall be defined in every translation unit in which it is implicitly instantiated (14.7.1) unless the corresponding specialization is explicitly instantiated (14.7.2) in some translation unit; no diagnostic is required.



但是然后说我尝试通过将模板的定义放在第二个翻译单元中来满足这些要求,并在两个位置之一包含显式实例化:
#include <iostream>

template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }

// Location 1    

template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }

// Location 2

消除显式实例化所指模板的歧义的规则是什么?将它放在位置 1 会导致正确的模板被实例化,并在最终程序中使用该定义,而将它放在位置 2 会实例化另一个模板,并导致我认为在 14.1 p6 以上的未定义行为。

另一方面,两个模板定义的隐式实例化无论如何都会选择第一个模板,因此在这些情况下消除模板歧义的规则似乎不同:
#include <iostream>

template<typename T>
T foo(T t) { std::cout << "A\n"; return 0; }

template<typename T>
int foo(int t) { std::cout << "B\n"; return 0; }

int main() {
    foo(1); // prints "A"
}

出现这种情况的原因与 this question 有关提问者发现单个前向声明
template<typename T>
T CastScriptVarConst(const ScriptVar_t& s);

不能作为多个模板定义的声明:
template<typename T>
typename std::enable_if<GetType<T>::value < SVT_BASEOBJ,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return (T) s;
}

template<typename T>
typename std::enable_if<!(GetType<T>::value < SVT_BASEOBJ)
                        && std::is_base_of<CustomVar,T>::value,T>::type
CastScriptVarConst(const ScriptVar_t& s) {
    return *s.as<T>();
}

我想更好地理解模板定义和声明之间的关系。

最佳答案

好,让我们从头开始。模板的“模板名称”是被模板化的函数或类的实际名称;也就是说,在

template<class T> T foo(T t);
foo是模板名称。对于函数模板,判断它们是否相同的规则很长,在14.5.5.1“函数模板重载”中有描述。当应用于涉及模板参数的表达式时,该部分的第 6 段(我在这里引用了 C++03,因此措辞和段落编号在 C++11 中可能已更改)定义了等效和功能等效的术语。

简而言之,等价表达式除了模板参数的名称可能不同外是相同的,如果它们碰巧计算出相同的东西,则功能等价表达式是相同的。比如前两个f声明是等效的,但第三个仅在功能上与其他两个等效:-
template<int A, int B>
void f(array<A + B>);
template<int T1, int T2>
void f(array<T1 + T2>);
template<int A, int B>
void f(array< mpl::plus< mpl::int<A>, mpl::int<B> >::value >);

第 7 段继续将这两个定义扩展到整个函数模板。匹配(在名称、范围和模板参数列表中)的两个函数模板如果它们也具有等效的返回类型和参数类型,则它们是等效的,或者如果它们只有功能等效的返回类型和参数类型,则它们在功能上是等效的。看看你的第二个例子,这两个函数只是在功能上等效:-
template<typename T>
T foo(T t);

template<typename T>
typename identity<T>::type foo(T t);

第 7 段以可怕的警告结束,“如果程序包含功能等效但不等效的函数模板声明,则该程序格式错误;不需要诊断。”因此,您的第二个示例不是有效的 C++。检测这样的错误需要在二进制文件中对函数模板的每个声明和定义进行注释,并使用 AST 来描述每个参数和返回类型来自的模板表达式,这就是标准不需要实现来检测它的原因。 MSVC 在按照您的意图编译您的第三个示例时是合理的,但也有理由打破。

继续进行显式实例化,重要的部分是 14.7,“模板实例化和特化”。第 5 款禁止以下所有行为:
  • 多次显式实例化模板;
  • 显式实例化和显式特化同一个模板;
  • 多次明确地为同一组参数指定模板。

  • 同样,“不需要诊断”,因为它很难检测到。

    因此,为了扩展您的显式实例化示例,以下代码违反了第二条规则并且是非法的:-
    /* Template definition. */
    template<typename T>
    T foo(T t)
    { ... }
    
    /* Specialization, OK in itself. */
    template< >
    int foo(int t)
    { ... }
    
    /* Explicit instantiation, OK in itself. */
    template< >
    int foo(int t);
    

    无论显式特化和显式实例化的位置如何,这都是非法的,但是当然因为不需要诊断,您可能会在某些编译器上获得有用的结果。还要注意显式实例化和显式特化之间的区别。以下示例格式错误,因为它声明了一个显式特化而没有定义它:-
    template<typename T>
    T f(T f)
    { ... }
    
    template< >
    int f(int);
    
    void g(void)
    { f(3); }
    

    但是这个例子是格式良好的,因为它有一个显式的实例化:-
    template<typename T>
    T f(T f)
    { ... }
    
    template f(int);
    
    void g(void)
    { f(3); }
    
    < >使一切变得不同。还要注意的是,即使您确实定义了显式特化,它也必须是 之前 您使用它,否则编译器可能已经为该模板生成了一个隐式实例化。这是在 14.7.3“显式特化”第 6 段中,就在您阅读的下方,同样,不需要诊断。为了适应相同的例子,这是格式错误的:-
    template<typename T>
    T f(T f)
    { ... }
    
    void g(void)
    { f(3); } // Implicitly specializes int f(int)
    
    template< >
    int f(int) // Too late for an explicit specialization
    { ... }
    

    如果您还不够困惑,请看一下您的最后一个示例:-
    template<typename T>
    T foo(T t) { ... }
    
    template<typename T>
    int foo(int t) { ... }
    
    foo的第二个定义是 不是 第一个定义的特化。它必须是 template< > int foo(int)成为template<typename T> T foo(T)的专业.但这没关系:允许函数重载,并且允许在函数模板和普通函数之间进行。表单调用 foo(3)将始终使用第一个定义,因为它的模板参数 T可以从参数类型推导出来。第二个定义不允许从参数类型推导出它的模板参数。只有明确指定 T你能达到第二个定义,只有当调用与第一个定义没有歧义时:-
    f<int>(3); // ambiguous
    f<string>(3); // can only be the second one
    

    对函数模板做重载解析的整个过程太长了,这里就不赘述了。如果您有兴趣并提出更多问题,请阅读第 14.8.3 节 :-)

    关于c++ - 模板定义如何与模板声明匹配?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9607089/

    相关文章:

    c++ - 在访问者模式中使用 accept()

    c++ - 抑制特定模板代码的编译器警告

    C++模板传入一个模板类,后面指定模板类型

    c++ - 如何使用 ESE CPP apis 从 ESE 数据库文件中读取字符串值?

    c++ - 如何调用模板类的静态成员?

    C++ STL 迭代器接口(interface)

    C++制作类容器类

    c++ - 使用 jpeg_read_raw_data 函数从 jpeg 文件中读取原始数据

    c++ - 无法从新函数中修改类变量

    c++ - 使用 HashMap 将点划分为子区域