【问题标题】:Why default argument can't be added later in template functions?为什么以后不能在模板函数中添加默认参数?
【发布时间】:2015-11-28 07:50:32
【问题描述】:

C++ 标准第 8.3.6.4 节这样说

对于非模板函数,可以在以后添加默认参数 同一范围内的函数声明。 [...]

但我的问题是,为什么模板函数不允许使用它?不允许在模板函数的相同范围内的后续声明中添加默认参数的理由是什么?

考虑这个编译良好的程序。 (非模板函数)(见现场演示here.

#include <iostream>

int f(int a,int b,int c=3);
int f(int a,int b=9,int c); // default argument in middle, ok allowed

int main()
{
    f(3);
    f(3,6);
    f(3,6,9);
    return 0;
}

int f(int a,int b,int c)
{
    std::cout<<a<<' '<<b<<' '<<c<<'\n';
    return 0;
}

但以下编译失败。 (模板函数)(见现场演示here.

#include <iostream>

template <typename T> 
void f(T a,int b,int c=3);
template <typename T> 
void f(T a,int b=9,int c); // compiler error why???

int main()
{
    f(3);
    f(3,6);
    f(3,6,9);
    return 0;
}

template <typename T> 
void f(T a,int b,int c)
{
    std::cout<<a<<' '<<b<<' '<<c<<'\n';
} 

【问题讨论】:

  • 缺少template &lt;typename T&gt;
  • Here's 一个几乎可以编译的例子
  • @PravasiMeet 我指的是第二个声明,除了中间参数的新默认参数之外,您还重新定义了最后一个参数的默认值。因为在 first 示例中,您没有重新定义 c 参数,所以在第二个示例中这样做是相当奇怪的。尤其是默认参数的正弦 重新定义 是它自己的编译错误。
  • 更好的问题可能是为什么它允许用于非模板函数...
  • @PravasiMeet 因为您要么声明或声明并定义模板化函数。离开template &lt;typename T&gt;,你是在声明(或声明和定义)一个没有模板参数的函数。

标签: c++ templates language-lawyer function-templates default-arguments


【解决方案1】:

这是在标准化过程的早期添加的历史限制(它在 C++98 中存在,但在 ARM 中没有)。

我不记得确切的原因(我的同事也不记得,在做出决定时他几乎肯定在场)。不过,我猜……

当时,一体式编译器通过解析重放标记来实例化模板。一些几乎没有解析过的模板。考虑:

template<class T> struct S {
  T f(T);  // (1)
};
template<class T> T S<T>::f(T p = 42) { return p; }  // (2)
S<int> s;  // Causes the "real" parsing of (1), but not (2).
int r = s.f();  // (3)

在解析调用 (3) 时,旧编译器因此通常只能访问实例化的声明 (1),而 (2) 仍未真正解析(只是标记缓冲)。因此,此类编译器不知道 (3) 中添加的默认参数。

怀疑是谨慎导致委员会因此决定更普遍地禁止在模板中添加默认参数。

今天,这种限制可能(技术上)不太合理,因为其他标准要求已经导致需要以通用形式解析模板(尽管,例如,MSVC 仍然不这样做 AFAICT)。也就是说,实现起来可能还是有点麻烦,因为默认参数现在可能必须在各种不同的上下文中实例化。

【讨论】:

  • V:(2)just token buffered是什么意思?
  • 不敢相信这个问题已经得到模板专家和 C++ 模板作者完整指南的回答:David Vandevoorde
  • 回复。 “缓冲令牌”... 一些编译器(MSVC、基于 EDG 的编译器、3.4 之前的 GCC 和大多数较旧的编译器,如 Cfront)通过“缓冲”它们组成的令牌来处理模板。然后在实例化时,这些标记通过解析器“重放”,除了模板参数标记被相应的模板参数替换。因此,模板可能处于“仅缓冲令牌;未解析”状态。
  • 有些东西我不太明白,即使假设那些较旧的编译器......所以,在解析 (3) 处的调用时,编译器如何找出数百个不是 -尚未解析的方法定义,如 (2) 它现在必须解析/实例化才能获得 S&lt;int&gt;::f(int)?一旦解析了类S&lt;int&gt; 的定义,它不应该有某种表将像(1)这样的成员声明映射到像(2)这样的定义吗?我认为它应该,如果只是为电话整理一份可行的候选人名单。如果是这样,那么从这些候选人那里收集默认参数似乎很容易,不是吗?
【解决方案2】:

因为这根本不可能。

为了实现你的目标,编译器必须实现一个函数,给定两个模板函数,返回它们是否是同一个函数。问题是这个函数无法实现。

对于常规的非模板函数,这是一个糟糕的想法,但仍然可行,因为您只需匹配参数类型并完成工作。

不幸的是,对于模板函数,这变得……更加棘手。考虑:

template<typename T> void f(T t);
template<typename U> std::enable_if_t<std::is_same<U, int>::value> f(U u = U());

您可以看到他们可能声明了相同的函数,如果 Tint。否则,他们不会。与默认模板参数和类似事物的交互存在更多问题,但长话短说,这对于模板来说是无法确定的。

【讨论】:

  • 编译器已经要做声明匹配;你完全可以多次声明同一个函数模板。
  • @T.C.:这实际上不需要匹配声明。
  • “与默认模板参数和类似事物的交互存在更多问题” AFAIK,您可以将默认模板参数添加到以后重新声明函数模板。这是你在暗示的吗?如果是这样,为什么这种情况不同?
  • 如果你多次声明一个函数模板,为了重载决议不产生歧义,声明是否必须匹配?
  • 这个功能不是因为(和根据)[temp.over.link]而需要实现的吗?特别是非规范的 p7
猜你喜欢
  • 1970-01-01
  • 2010-11-10
  • 2021-09-03
  • 1970-01-01
  • 2013-05-31
  • 2020-03-30
  • 1970-01-01
  • 2011-10-01
  • 1970-01-01
相关资源
最近更新 更多