模板定义如何与模板声明相匹配?

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

例如,下面的程序工作正常。

#include <iostream>

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

int main() {
    foo(1);
}

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

如果我改变了在模板定义中使用模板参数的方式,名称显然不再引用相同的模板,并且链接失败。

#include <iostream>

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

int main() {
    foo(1);
}

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

// or

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

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

接下来,如果我将模板定义移动到另一个翻译单元,为了实现C ++(MSVC 11测试版),无论我怎么说类型,程序都能正常工作。

//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 << "An"; return 0; }

template int foo<int>(int);

要么

//definition.cpp
#include <iostream>

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

template int foo<int>(int);

或者即使定义完全不是模板:

//definition.cpp
#include <iostream>

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

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

§14.1 [temp] p6

一个类模板的函数模板,类模板的成员函数或类模板的静态数据成员应该在其隐式实例化的每个翻译单元中定义(14.7.1),除非相应的特化被明确实例化(14.7.2)一些翻译单位; 不需要诊断。

但是接下来说我试图通过在第二个翻译单元中定义模板来满足这些要求,并且在两个位置之一包括一个明确的实例:

#include <iostream>

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

// Location 1    

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

// Location 2

有关明确实例化引用的模板的歧义的规则是什么? 将它放在位置1会导致实例化正确的模板,并将该定义用于最终的程序,同时将它放在位置2处实例化另一个模板,并导致我认为在上面14.1 p6下未定义的行为。

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

#include <iostream>

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

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

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

这个问题的原因与提问者发现单个前向声明的这个问题有关

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节,如果你有兴趣并提出更多问题:-)

    链接地址: http://www.djcxy.com/p/58109.html

    上一篇: How are template definitions matched to template declarations?

    下一篇: C#: Change Retrieval DataType for Entity Framework