Clang 不会注意到默认的模板参数

Clang does not notice default template parameters

背景

根据C++标准,当使用默认模板参数前向声明模板类型时,它们中的每一个只能出现在一个声明中。例如:

// GOOD example
template <class T = void>
class Example;    // forward-declaration

template <class T>
class Example {}; // definition
// GOOD example
template <class T>
class Example;    // forward-declaration

template <class T = void>
class Example {}; // definition
// BAD example
template <class T = void>
class Example;    // forward-declaration

template <class T = void> // ERROR: template parameter redefines default argument
class Example {}; // definition

问题

在我的代码中,我在不同的文件中有很多前向声明,所以将我的默认参数放在定义中是有意义的:

// foo.hpp, bar.hpp, baz.hpp, etc.
template <class T>
class Example;
// example.hpp
template <class T = void>
class Example {};

而且,正如预期的那样,它在任何地方都运行良好...除了 clang!我将问题缩小为:
在 clang 中,如果 class 模板有默认参数,但它们没有在那个 class 的 的第一个前向声明 中声明,并且在声明那个的实例时class 未指定尖括号,clang 忽略默认参数并引发错误 "no viable constructor or deduction guide for deduction of template arguments of ..."。

例子

// GOOD example
template <class T>
class Example;

template <class T = void>
class Example {};

int main() {
    Example e; // error: no viable constructor or deduction guide for deduction of template arguments of 'Example'
}

问题

在这种情况下谁是对的:clang 还是其他编译器?这是编译器错误吗?我怎样才能避免这个问题(除了我上面描述的部分解决方案)? 我找到了 #10147(以及相关的 Whosebug 问题),但它是关于模板模板参数的,并且在一年前也被标记为已修复。

编辑

这看起来像是一个错误,现在已在 LLVM 错误追踪器 (#40488) 上报告。

我不知道谁是对的但是...

How can I circumvent this issue (apart from partial solutions I described above)?

添加以下扣除规则怎么样?

Example() -> Example<>;

以下代码使用 g++ 和 clang++ 编译(显然是 C++17)

template <class T>
class Example;

template <class T = void>
class Example {};

Example() -> Example<>;

int main() {
    Example e;
}

标准不区分默认模板参数是在模板的定义中定义还是在模板声明中定义。

因为当默认参数出现在声明中而不是定义中时,Clang 接受代码,所以这两种行为中至少有一种是错误的。考虑到 [over.match.class.deduct]/1.1.1:

The template parameters are the template parameters of C followed by the template parameters (including default template arguments) of the constructor, if any.

,我很想说Clang应该使用默认的模板参数。

我认为您可以通过以下常见做法避免此错误:

  1. 如果必须转发声明,请为该转发声明创建专用头文件。

  2. 在此前向声明文件中定义默认参数

  3. 还将此文件包含在提供模板定义的头文件中。

示例参见 iosfwdlibstdc++/iosfwd

考虑以下因素:

[temp.param]/12 - The set of default template-arguments available for use is obtained by merging the default arguments from all prior declarations of the template in the same way default function arguments are [ Example:

template<class T1, class T2 = int> class A;
template<class T1 = int, class T2> class A;

is equivalent to

template<class T1 = int, class T2 = int> class A;

— end example ]

可用于

的默认参数
template <class T>
class Example;

template <class T = void>
class Example {};

将是 Example 定义中的默认参数。上面的两个声明相当于有一个声明

template <class T = void>
class Example {};

这将有效地允许 Example e

应接受原始代码。作为解决方法并且已在 中建议,您可以提供使用默认参数的推导指南

Example() -> Example<>;