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

时间:2012-03-07 18:29:38

标签: c++ templates

模板声明与模板定义的匹配程度如何?我在标准中发现了一些关于 template-ids 的文本,如果“他们的模板名称 [...]引用相同的模板和[.. 。“”(14.4 [temp.type] p1)但我找不到模板名称的定义,或者当 template-names 引用同一模板时。我不确定我是否在正确的轨道上,因为我没有充分破译语法,无法判断 template-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

     

函数模板,类模板的成员函数或静态   每个翻译中都应定义类模板的数据成员   它被隐式实例化的单位(14.7.1),除非   相应的专业化被明确地实例化(14.7.2)in   一些翻译单位;无需诊断。

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

#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>();
}

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

1 个答案:

答案 0 :(得分:4)

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

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

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

简而言之,等效表达式除了可能具有不同的模板参数名称之外是相同的,并且功能等效的表达式如果碰巧评估为相同则相同事情。例如,前两个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节: - )