【问题标题】:Clang does not notice default template parametersClang 不会注意到默认模板参数
【发布时间】:2019-06-18 11:27:57
【问题描述】:

背景

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

// 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中,如果类模板具有默认参数,但它们没有在该类的第一个前向声明中声明,并且在声明该类的实例时没有指定尖括号 em>,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'
}
  • = void 移至前向声明可解决此问题,但对我来说不可行,因为我的前向声明位于不同的文件中,我不知道哪个会首先出现。 (这也是超级问题,因为我的默认值会在代码库深处的一些不起眼的文件中)
  • Example e; 更改为Example&lt;&gt; e; 可以解决此问题,但对我来说不可行,因为我是一名图书馆开发人员,并且不希望我的所有用户在我的课后都输入&lt;&gt;
  • 添加带有一个前向声明的前向声明文件example_fwd.hpp 并包含它而不是每次都前向声明可以解决问题,但如果有更好的解决方案,我想避免这种情况。

问题

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

编辑

这看起来像一个错误,现在在 LLVM bugtracker (#40488) 上报告。

【问题讨论】:

  • 扣分指南能解决吗?看来at least in your example...
  • 不确定 SO 是否是此错误报告的正确位置。 LLVM 支持可能更适合于此。
  • @SergeyA 但这是确认这是一个错误的理想场所,特别是因为 OP 不确定。
  • 我不知道是谁在介绍演绎指南,但this 点在铿锵声中

标签: c++ templates clang default-arguments


【解决方案1】:

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

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

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

Example() -> Example<>;

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

template <class T>
class Example;

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

Example() -> Example<>;

int main() {
    Example e;
}

【讨论】:

    【解决方案2】:

    考虑以下几点:

    [temp.param]/12 - 可用的默认模板参数集是通过以相同的方式合并模板的所有先前声明的默认参数来获得的,默认函数参数是 [ 示例

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

    等价于

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

    — 结束示例 ]

    可用于

    的默认参数
    template <class T>
    class Example;
    
    template <class T = void>
    class Example {};
    

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

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

    这将有效地允许Example e

    应接受原始代码。作为一种解决方法,并且已经在max66's answer 中提出建议,您可以提供使用默认参数的演绎指南

    Example() -> Example<>;
    

    【讨论】:

    • 我接受这个而不是max66's answer,因为你是第一个解释为什么这是一个错误的人。对于更一般的情况,可以改进推导指南:template &lt;class T = void, class... Args&gt; Example(Args&amp;&amp;...) -&gt; Example&lt;&gt; 以接受任意数量的任何类型的参数。看起来我正在提交错误报告。
    • "这将有效地允许执行Example e。" - 但是 NatanOliver 指出的 part of the standard 呢?
    • @max66 NathanOliver 指出的部分不处理启用 CTAD 的说明符 simple type specifier。不包括指针类型。
    • @max66 也请注意,以前的 clang 版本甚至不接受 Example e,但后来发生了变化。 bugs.llvm.org/show_bug.cgi?id=32443
    【解决方案3】:

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

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

    模板参数是C的模板参数,后面跟着构造函数的模板参数(包括默认模板参数),如果有的话。

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

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

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

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

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

    作为示例,请参阅iosfwd:libstdc++/iosfwd

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-28
      • 1970-01-01
      • 1970-01-01
      • 2014-08-06
      相关资源
      最近更新 更多