Clang没有注意到默认的模板参数

and*_*sxp 14 c++ templates clang default-arguments

背景

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

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

template <class T>
class Example {}; // definition
Run Code Online (Sandbox Code Playgroud)
// GOOD example
template <class T>
class Example;    // forward-declaration

template <class T = void>
class Example {}; // definition
Run Code Online (Sandbox Code Playgroud)
// BAD example
template <class T = void>
class Example;    // forward-declaration

template <class T = void> // ERROR: template parameter redefines default argument
class Example {}; // definition
Run Code Online (Sandbox Code Playgroud)

问题

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

// foo.hpp, bar.hpp, baz.hpp, etc.
template <class T>
class Example;
Run Code Online (Sandbox Code Playgroud)
// example.hpp
template <class T = void>
class Example {};
Run Code Online (Sandbox Code Playgroud)

而且,正如所料,它在所有地方都运作良好......除了铿锵!我把问题缩小到这个:
在clang中,如果类模板有默认参数,但它们没有在该类的第一个前向声明中声明,并且在声明该类的实例时没有指定尖括号,则clang忽略默认参数并引发错误"没有可行的构造函数或扣除指南来扣除...的模板参数".

// 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'
}
Run Code Online (Sandbox Code Playgroud)
  • 转到= void前面的声明修复了这个问题,但是对我来说不可行,因为我的forward-decl是在不同的文件中,我不知道哪一个会先出现.(也是超级问题,因为我的默认值将在代码库深处的一些模糊文件中)
  • 更改Example e;Example<> e;解决问题,但对我来说不可行,因为我是一个库开发人员并且不希望我的所有用户<>在我的课程后键入.
  • 添加一个前向声明文件example_fwd.hpp,其中包含一个前向声明并包含它而不是每次都向前声明修复问题,但如果有更好的解决方案,我想避免这种情况.

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

编辑

这看起来像一个bug,现在报告在LLVM bugtracker(#40488)上.

max*_*x66 5

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

我怎样才能绕过这个问题(除了我上面描述的部分解决方案)?

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

Example() -> Example<>;
Run Code Online (Sandbox Code Playgroud)

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

template <class T>
class Example;

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

Example() -> Example<>;

int main() {
    Example e;
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*ans 3

考虑以下因素:

\n\n
\n

[temp.param]/12 - 可用的默认模板参数集是通过合并模板的所有先前声明中的默认参数来获得的,就像默认函数参数 [\xe2\x80\x89示例

\n\n
template<class T1, class T2 = int> class A;\ntemplate<class T1 = int, class T2> class A;\n
Run Code Online (Sandbox Code Playgroud)\n\n

相当于

\n\n
template<class T1 = int, class T2 = int> class A;\n
Run Code Online (Sandbox Code Playgroud)\n\n

\xe2\x80\x94\xe2\x80\x89结束示例]

\n
\n\n

可用的默认参数

\n\n
template <class T>\nclass Example;\n\ntemplate <class T = void>\nclass Example {};\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
template <class T = void>\nclass Example {};\n
Run Code Online (Sandbox Code Playgroud)\n\n

这将有效地允许做Example e

\n\n

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

\n\n
Example() -> Example<>;\n
Run Code Online (Sandbox Code Playgroud)\n

  • 我接受这个而不是[max66的答案](/sf/answers/3804690081/),因为你是第一个解释为什么这是一个错误的人。对于更一般的情况,可以改进推导指南:“template &lt;class T = void, class...Args&gt;Example(Args&amp;&amp;...) -&gt;Example&lt;&gt;”以接受任意数量的任何类型的参数。看起来我正在提交错误报告。 (2认同)