Clang 无法在 std::function 实例化中扩展参数包 [英] Clang fails to expand parameter pack in std::function instantiation

查看:43
本文介绍了Clang 无法在 std::function 实例化中扩展参数包的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

The snippet of code compiled with std=c++17 as the only compiler flag ...

  • ... 使用 GCC 9.1 成功编译.Godbolt
  • ... 使用 Clang 8.0.0 发出编译器错误(错误在代码段下方).Godbolt

问题:这是 Clang 编译器中的一个错误,还是 GCC 错误地接受了这段代码,或者是其他什么东西在起作用?

Question: is this a bug in the Clang compiler or is GCC wrong to accept this code or is something else at play?

#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 但它错误地将 别名化函数std::function一样,只有一个而不是三个参数.


What strikes me as odd is that Clang's error indicates it successfully aliased Tuple as std::tuple<int, int, int> but it wrongly aliases Function as just std::function<void(int)>, with only one instead of three arguments.

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){});

<小时>

其他研究

正如评论中的其他用户已经指出的,使 Int 模板别名依赖于 T 类型解决了这个问题:


Additional Research

As other users in the comments already pointed out, making the Int template alias dependent on the type T fixes the issue:

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

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

Something else I found out, just referring to the Function type from the outside also makes it work as expected/desired:

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

推荐答案

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

TL;DR: it's a clang bug, but there's also a bug in the standard.

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

First be aware that in C++, templates are handled in two steps:

  1. 在定义封闭模板时构建不依赖模板参数的结构.
  2. 依赖的结构是在实例化封闭模板时构建的.

现在,似乎clang处理了std::function<void(Int<Ts>...) > 作为非依赖类型,其推理如下:

Now, it seems that clang treats std::function< void(Int<Ts>...) > as a non-dependent type, with the following reasoning:

  1. Int 是非依赖类型(正确).
  2. 因此,包含Int(即Int...)的包扩展也是一个非依赖的类型"(?).
  3. 因为void(Int<Ts>...)的所有组件都是非依赖的,所以是非依赖类型(显然不正确).
  4. 因为名称 std::function 是非依赖的,模板参数 void(Int<Ts>...) 是一个非依赖的非包-扩展类型,std::function...) > 是非依赖类型.
  1. Int<Ts> is a non-dependent type (correct).
  2. Thus, pack expansion containing Int<Ts> (i.e. Int<Ts>...) is also a non-dependent "type" (?).
  3. Because all components of void(Int<Ts>...) are non-dependent, it is a non-dependent type (obviously incorrect).
  4. Because the name std::function is non-dependent and the template argument void(Int<Ts>...) is a non-dependent non-pack-expansion type, std::function< void(Int<Ts>...) > is a non-dependent type.

(请注意,非包扩展"检查使其与 Tuple 情况不同.)

(Note that the "non-pack-expansion" check makes it different from the Tuple case.)

因此,当定义 Foo 时,类型名称 Function 被视为命名非依赖类型,并立即构建,并进行包扩展(发生在实例化)没有被计算在内.因此,Function 的所有使用都被脱糖"类型 std::function< 替换.void(int) >.

Therefore, when Foo is defined, the type name Function is treated as naming a non-dependent type, and is immediately built, and pack expansion (which happens during instantiation) is not accounted. Consequently, all uses of Function is replaced with the "desugared" type std::function< void(int) >.

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

Furthermore, clang has the notion of instantiation-dependent, which means the construct is not dependent, but it still somehow involves the template parameters (e.g. the construct is only valid for some parameters). std::function< void(Int<Ts>...) > is treated as a instantiation-dependent type, so when the template is instantiated, clang still performs substitution for using Function = std::function< void(Int<Ts>...) >. As a result, Function gets the correct type, but this does not propagate to uses of Function in the definition of Foo.

现在是标准中的错误.

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

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

A type is dependent if it is

  • 模板参数,
  • 未知专业的成员,
  • 作为当前实例的依赖成员的嵌套类或枚举,
  • 一个 cv 限定类型,其中 cv 非限定类型是依赖的,
  • 由任何依赖类型构造的复合类型,
  • 一个数组类型,其元素类型是相关的,或者其边界(如果有)是值相关的,
  • 异常规范依赖于值的函数类型,
  • simple-template-id 表示,其中模板名称是模板参数或任何模板参数是从属类型或依赖于类型或值的表达式或一个包扩展 [ 注意:这包括一个注入的类名在没有模板参数列表的情况下使用的类模板.— 尾注 ] ,或
  • decltype(expression) 表示,其中表达式依赖于类型.
  • a template parameter,
  • a member of an unknown specialization,
  • a nested class or enumeration that is a dependent member of the current instantiation,
  • a cv-qualified type where the cv-unqualified type is dependent,
  • a compound type constructed from any dependent type,
  • an array type whose element type is dependent or whose bound (if any) is value-dependent,
  • a function type whose exception specification is value-dependent,
  • denoted by a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion [ Note: This includes an injected-class-name of a class template used without a template-argument-list. — end note ] , or
  • denoted by decltype(expression), where expression is type-dependent.

请注意,它并没有说参数列表包含包扩展的函数类型是依赖类型,只是说从任何依赖类型构造的复合类型"和异常规范是值依赖的函数类型"是依赖的.两者都没有帮助.

Note that it doesn't say that a function type whose parameter list contains pack expansions is a dependent type, only that "a compound type constructed from any dependent type" and "a function type whose exception specification is value-dependent" are dependent. Neither is helpful here.

这篇关于Clang 无法在 std::function 实例化中扩展参数包的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆