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

bam*_*s53 7 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; }
Run Code Online (Sandbox Code Playgroud)

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

#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; }
Run Code Online (Sandbox Code Playgroud)

接下来,如果我将模板定义移动到另一个翻译单元,为了实现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);
Run Code Online (Sandbox Code Playgroud)

要么

//definition.cpp
#include <iostream>

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

template int foo<int>(int);
Run Code Online (Sandbox Code Playgroud)

或者即使定义根本不是模板:

//definition.cpp
#include <iostream>

int foo(T t) { std::cout << "A\n"; return 0; }
Run Code Online (Sandbox Code Playgroud)

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

§14.1[temp] p6

除非明确地实例化(14.7.2)相应的特化,否一些翻译单位; 无需诊断.

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

#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
Run Code Online (Sandbox Code Playgroud)

关于消除显式实例化所指的模板的歧义有哪些规则?将它放在位置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"
}
Run Code Online (Sandbox Code Playgroud)

这个问题出现的原因与提问者发现单一前瞻性声明的问题有关

template<typename T>
T CastScriptVarConst(const ScriptVar_t& s);
Run Code Online (Sandbox Code Playgroud)

不能作为多个模板定义的声明:

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>();
}
Run Code Online (Sandbox Code Playgroud)

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

Dan*_*lme 4

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

template<class T> T foo(T t);
Run Code Online (Sandbox Code Playgroud)

foo是模板名称。对于函数模板,判断是否相同的规则相当长,如14.5.5.1“函数模板重载”中所述。当应用于涉及模板参数的表达式时,该部分的第 6 段(我在这里引用 C++03,因此 C++11 中的措辞和段落编号可能已更改)定义了术语equalfunctionly equal

简而言之,除了模板参数可能具有不同的名称之外,等效表达式是相同的,并且如果它们碰巧求值相同,则功能等效表达式是相同的。例如,前两个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 >);
Run Code Online (Sandbox Code Playgroud)

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

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

template<typename T>
typename identity<T>::type foo(T t);
Run Code Online (Sandbox Code Playgroud)

第 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);
Run Code Online (Sandbox Code Playgroud)

无论显式专业化和显式实例化的位置如何,这都是非法的,但当然,因为不需要诊断,所以您可能会在某些编译器上获得有用的结果。另请注意显式实例化和显式专业化之间的区别。以下示例格式不正确,因为它声明了显式专业化但没有定义它:-

template<typename T>
T f(T f)
{ ... }

template< >
int f(int);

void g(void)
{ f(3); }
Run Code Online (Sandbox Code Playgroud)

但这个例子是格式良好的,因为它有一个显式的实例化:-

template<typename T>
T f(T f)
{ ... }

template f(int);

void g(void)
{ f(3); }
Run Code Online (Sandbox Code Playgroud)

< >让一切变得不同。还要注意的是,即使您确实定义了显式专门化,也必须在使用它之前进行,否则编译器可能已经为该模板生成了隐式实例化。这是 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
{ ... }
Run Code Online (Sandbox Code Playgroud)

如果您还不够困惑,请看一下最后一个示例:-

template<typename T>
T foo(T t) { ... }

template<typename T>
int foo(int t) { ... }
Run Code Online (Sandbox Code Playgroud)

的第二个定义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
Run Code Online (Sandbox Code Playgroud)

对函数模板进行重载解析的整个过程太长,这里无法描述。如果您感兴趣并提出更多问题,请阅读第 14.8.3 节:-)