为什么C ++ 03需要模板参数来进行外部链接?

时间:2013-01-30 05:43:40

标签: c++ templates

背景

在C ++ 03中,用作模板参数的符号必须具有外部链接;正如this previous question中所探讨的那样,在C ++ 11中删除了这个限制:

  

在C ++ 03中,模板参数不能具有内部链接:

     
    

[C++03: 14.6.4.2/1]:对于依赖于模板参数的函数调用,如果函数名称是 unqualified-id 而不是 template-id ,则使用通常的查找规则(3.4.1,3.4.2)找到候选函数,但:

         
        
  • 对于使用非限定名称查找的部分查找(3.4.1),仅找到模板定义上下文中带有外部链接的函数声明
  •     
  • 对于使用关联命名空间(3.4.2)的查找部分,只能找到在模板定义上下文或模板实例化上下文中找到的具有外部链接的函数声明。
  •     
         

[..]

  
     

在C ++ 11中更改了(问题#561: "Internal linkage functions in dependent name lookup"):

     
    

[C++11: C.2.6]: 14.6.4.2
    更改:允许具有内部链接的功能的相关调用
    理由:过度约束,简化了重载决策规则。

  
     

导致:

     
    

[C++11: 14.6.4.2/1]:对于依赖于模板参数的函数调用,使用通常的查找规则(3.4.1,3.4.2,3.4.3)找到候选函数,除了:

         
        
  • 对于使用非限定名称查找(3.4.1)或限定名称查找(3.4.3)的查找部分,只能找到模板定义上下文中的函数声明。 /强>
  •     
  • 对于使用关联命名空间(3.4.2)的查找部分,只能找到模板定义上下文或模板实例化上下文中找到的函数声明。
  •     
         

[..]

  
     

(找出缺少的“与外部链接”资格。)

Issue #561 ("Internal linkage functions in dependent name lookup"),导致在C ++ 11中删除限制的提案,请求:

  

此外,是否真的有必要从查找中排除内部链接功能? ODR是否给出了实现足够的自由度来处理这种情况而不会在名称查找上出现另一个问题?

后面的回答:

  

该小组的共识是,应该通过查找找到 [..] 内部链接函数(尽管如果通过重载解析选择它们可能会导致错误)。


问题

限制的最初实践理由是什么?

似乎必定有一个,因为最初的标准措辞不会限制查找到具有外部链接的符号。

是否只是“[内部联动功能]如果通过重载决议选择可能会导致错误”,并且通过2000年代的意见改变了这个有多重要?或者做了一些其他的改变,也许是因为其他地方针对不同的C ++ 11特征的新措辞的间接结果?

2 个答案:

答案 0 :(得分:8)

我怀疑它与C ++ 98的臭名昭着的export模板功能有关。想一想。一旦你允许模板定义出现在单独的翻译单元中,但是在指定模板参数之前仍然无法真正编译(即模板被实例化),你进入这个TU模板定义的暮光区域和具有实例化的TU必须遵守链接器可见性规则(即,分离模型),同时在重载决策方面共享它们的上下文。该问题的解决方案是仅在依赖名称查找中允许具有外部链接的函数。

这是一个例子。导出模板的一个鲜为人知的“特性”是,您可以在模板的TU中拥有一些具有内部链接的函数或类(即标记为static或未命名的命名空间)。如果具有实例化的TU也具有内部链接功能,一个与模板的TU中的模糊不一致或可能取代的内容?这是一个超现实的问题,我知道,这是导出模板的奇异世界。避免非常令人惊讶的行为的唯一方法是从查找中排除所有内部链接函数。还要考虑到没有人清楚地知道如何实际实现导出的模板,如果没有这个限制,实现它似乎更加不可能。

因此,一旦导出模板出来,对依赖名称查找的限制似乎显然毫无用处,并且在没有太多争议的情况下被取出。至少,这对我来说是完全合理的,但是,当然,这是猜测。

这是一个具体的例子:

// in exptemp.h
export template <typename T> bool is_valid(T value);

// in exptemp.cpp
namespace {
  bool is_space(char c) {
    return (c == ' ') || (c == '\t');
  };
};

template <typename T>
bool is_valid(T value) {
  return is_space(value);
};

// in test.cpp
#include "exptemp.h"

namespace {
  bool is_space(char c) {
    return (c == ' ') || (c == '\t') || (c == '\n');
  };
};

int main() {
  char c = '\n';
  return is_valid(c);   // will this return 0 or 1 ?!?!?
};

答案 1 :(得分:3)

据我所知,它纯粹是历史性的 - 它似乎最初被禁止,因为cfront的名称损坏不足以正确处理它。

有一次,安东尼·威廉姆斯写道a paper提出允许这样做,并说明如何去做 - 但是AFAIK,这篇论文从未被接受过,也没有将其要求编入标准。我怀疑这与时间问题一样重要。它是在2001年提出的,所以他们当时的工作(C ++ 2003)并不打算添加很多新材料,当他们开始认真地使用C ++ 11时,它似乎主要是遗忘。