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

7
一个模板声明如何与模板定义匹配?我在标准中找到了一些关于“模板ID”(template-ids)的文本,如果“它们的模板名(template-names)引用相同的模板,则引用相同函数。”(14.4 [temp.type] p1),但我找不到“模板名”的定义或者何时“模板名”会引用相同的模板。我不确定自己是否正确,因为我还没有完全理解语法,无法确定“模板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); 不需要进行诊断。
但如果我尝试通过在第二个翻译单元中放置模板的定义,并在两个位置之一包含显式实例化来满足这些要求,会怎样呢?
#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"
}

这个出现的原因与这个问题有关:这个问题,在那里问问题的人发现一个单一的前向声明。
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>();
}

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

请购买《C++ Templates: The Complete Guide》以获取完整的故事。是的,它比标准版本更易于理解...实际上更多 ;) - 0xC0000022L
1个回答

4

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

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);

第七段警告说:“如果程序包含声明了功能上等但不等效的函数模板,则该程序是无效的。不需要诊断。” 因此,您的第二个示例不符合有效的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节并提出更多问题 :-)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接