Clang无法在std :: function实例化中扩展参数包

时间:2019-07-17 16:23:42

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

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

  • ...使用GCC 9.1成功编译。 Godbolt
  • ...发出Clang 8.0.0的编译器错误(代码段下方的错误)。 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<int, int, int>,但错误地将Function的别名别名为std::function<void(int)>,只有一个而不是三个争论。

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可以解决此问题:

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

1 个答案:

答案 0 :(得分:1)

TL; DR:这是一个叮叮当当的错误,但标准中也存在一个错误。

首先请注意,在C ++中,模板的处理分为两个步骤:

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

现在,似乎clang将std::function< void(Int<Ts>...) >视为非依赖类型,其原因如下:

  1. Int<Ts>是非依赖性类型(正确)。
  2. 因此,包含Int<Ts>(即Int<Ts>...)的包扩展也是非依赖的“类型”(?)。
  3. 由于void(Int<Ts>...)的所有组件都是非依赖的,因此它是非依赖的类型(显然是错误的)。
  4. 因为名称std::function是非依赖性的,并且模板参数void(Int<Ts>...)是非依赖性的非压缩扩展类型,所以std::function< void(Int<Ts>...) >是非依赖性的类型。
  5. li>

(请注意,“ non-pack-expansion”检查使其不同于Tuple情况。)

因此,在定义Foo时,类型名称Function被视为命名非依赖类型,并立即生成,并且不考虑包扩展(在实例化期间发生)。因此,Function的所有用法都被替换为“ {desugared”类型的std::function< void(int) >

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


现在是标准中的错误。

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

  

类型是否依赖于

     
      
  • 模板参数
  •   
  • 专业领域未知的成员
  •   
  • 作为当前实例的从属成员的嵌套类或枚举,
  •   
  • 不符合简历类型的cv限定类型,
  •   
  • 从任何从属类型构造的复合类型
  •   
  • 一种数组类型,其元素类型是相关的,或者其边界(如果有)是值相关的,
  •   
  • 一种异常类型与值有关的函数类型,
  •   
  • simple-template-id 表示,其中模板名称是模板参数,或者任何模板参数是从属   类型或表达式,它取决于类型或值,或者   包扩展[注:这包括一个注入的类名   没有 template-argument-list 的类模板。 — [尾注],   或
  •   
  • decltype(expression)表示,其中表达式是类型相关的。
  •   

请注意,它不是说参数列表包含压缩扩展的函数类型是从属类型,而只是说“由任何从属类型构造的复合类型”和“异常说明与值有关的函数类型”是依赖的。两者都没有帮助。