【问题标题】:Clang fails to expand parameter pack in std::function instantiationClang 无法在 std::function 实例化中扩展参数包
【发布时间】:2019-11-26 13:22:55
【问题描述】:

使用std=c++17作为唯一编译器标志编译的代码的sn-p ...

  • ... 使用 GCC 9.1 成功编译。 Godbolt
  • ... 使用 Clang 8.0.0 发出编译器错误(低于 sn-p 的错误)。 Godbolt

问题:这是 Clang 编译器中的一个错误,还是 GCC 错误地接受了此代码,还是其他原因?

#include <functional>
#include <tuple>

template <typename... Ts>
struct Foo
{
    template <typename T>
    using Int = int;

    // Function that accepts as many 'int' as there are template parameters
    using Function = std::function< void(Int<Ts>...) >;

    // Tuple of as many 'int' as there are template parameters
    using Tuple = std::tuple< Int<Ts>... >;

    auto bar(Function f)
    {
        std::apply(f, Tuple{}); // Error with Clang 8.0.0
    }
};

int main()
{
    auto foo = Foo<char, bool, double>{};
    foo.bar([](int, int, int){});
}

让我感到奇怪的是,Clang 的错误表明它成功地将 Tuple 别名为 std::tuple&lt;int, int, int&gt;,但它错误地将 Function 别名为 std::function&lt;void(int)&gt;,只有一个而不是三个参数。

In file included from <source>:2:
In file included from /opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/functional:54:
/opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/tuple:1678:14: error: no matching function for call to '__invoke'
      return std::__invoke(std::forward<_Fn>(__f),
             ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/tuple:1687:19: note: in instantiation of function template specialization 'std::__apply_impl<std::function<void (int)> &, std::tuple<int, int, int>, 0, 1, 2>' requested here
      return std::__apply_impl(std::forward<_Fn>(__f),
                  ^
<source>:19:14: note: in instantiation of function template specialization 'std::apply<std::function<void (int)> &, std::tuple<int, int, int> >' requested here
        std::apply(f, Tuple{}); // Error
             ^
<source>:26:9: note: in instantiation of member function 'Foo<char, bool, double>::bar' requested here
    foo.bar([](int, int, int){});

其他研究

正如 cmets 中的其他用户已经指出的那样,使 Int 模板别名依赖于 T 类型可以解决问题:

template <typename T>
using Int = std::conditional_t<true, int, T>;

我发现的其他东西,只是从外部引用 Function 类型也使它按预期/期望工作:

int main()
{
    auto f = Foo<char, bool, double>::Function{};
    f = [](int, int, int){};
}

【问题讨论】:

  • 对我来说是个 bug。代码似乎没有错误。
  • 还有为什么std::apply 不为我工作-.-
  • @LightnessRacesinOrbit 是的,我还通过从类外部引用Foo&lt;…&gt;::Function 发现了这一点,然后它表现为一个采用三个整数的函数......甚至Tuple 别名也可以正常工作.那么它就是一个错误!
  • 更新:为此记录了错误:bugs.llvm.org/show_bug.cgi?id=42654(正如 Maarten 亲自问我的那样)

标签: c++ gcc clang c++17 variadic-templates


【解决方案1】:

TL;DR:这是一个 clang 错误,但标准中也有一个错误。

首先要知道,在 C++ 中,模板分两步处理:

  1. 依赖模板参数的构造是在定义封闭模板时构建的。
  2. 在实例化封闭模板时构建相关的构造。

现在,clang 似乎将std::function&lt; void(Int&lt;Ts&gt;...) &gt; 视为非依赖类型,原因如下:

  1. Int&lt;Ts&gt; 是非依赖类型(正确)。
  2. 因此,包含Int&lt;Ts&gt;(即Int&lt;Ts&gt;...)的包扩展也是一个非依赖的“类型”(?)。
  3. 因为void(Int&lt;Ts&gt;...)的所有组件都是非依赖的,所以它是非依赖类型(显然不正确)。
  4. 因为名称std::function 是非依赖型,而模板参数void(Int&lt;Ts&gt;...) 是非依赖型非打包扩展类型,所以std::function&lt; void(Int&lt;Ts&gt;...) &gt; 是非依赖型。

(请注意,“非打包扩展”检查使其与 Tuple 的情况不同。)

因此,当定义Foo 时,类型名称Function 被视为命名非依赖类型,并立即构建,并且不考虑包扩展(在实例化期间发生)。因此,Function 的所有用法都被替换为“去糖”类型 std::function&lt; void(int) &gt;

此外,clang 有 instantiation-dependent 的概念,这意味着构造是不依赖的,但它仍然以某种方式涉及模板参数(例如,构造仅对某些参数有效)。 std::function&lt; void(Int&lt;Ts&gt;...) &gt; 被视为依赖于实例化的类型,所以当模板被实例化时,clang 仍然对using Function = std::function&lt; void(Int&lt;Ts&gt;...) &gt; 进行替换。结果,Function 获得了正确的类型,但这不会传播到 FunctionFoo 的定义中的使用。


现在这是标准中的错误。

类型是否依赖在[temp.dep.type]中定义:

一个类型是依赖的,如果它是

  • 模板参数,
  • 未知专业的成员,
  • 一个嵌套类或枚举,它是当前实例化的依赖成员,
  • 一个 cv 限定类型,其中 cv 非限定类型是依赖的,
  • 由任何依赖类型构成的复合类型,
  • 一种数组类型,其元素类型是相关的,或者其边界(如果有的话)是值相关的,
  • 异常规范依赖于值的函数类型,
  • simple-template-id 表示,其中模板名称是模板参数或任何模板参数是依赖项 类型或依赖于类型或值的表达式,或者是 一个包扩展 [ 注意:这包括一个注入的类名 在没有 template-argument-list 的情况下使用的类模板。 — 尾注 ] , 或
  • decltype(expression) 表示,其中表达式取决于类型。

请注意,它并没有说参数列表包含包扩展的函数类型是依赖类型,只是说“由任何依赖类型构造的复合类型”和“异常规范依赖于值的函数类型”是依赖的。在这里都没有帮助。

【讨论】:

  • 一件困难的事情,但你很好地解释了这两个错误!我认为你的推理没有错误,所以我会接受这个作为正确答案。
猜你喜欢
  • 1970-01-01
  • 2018-06-07
  • 1970-01-01
  • 2016-07-05
  • 1970-01-01
  • 1970-01-01
  • 2013-03-30
  • 2011-10-08
  • 1970-01-01
相关资源
最近更新 更多