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

Maa*_*lis 12 c++ gcc clang variadic-templates c++17

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

  • ...使用GCC 9.1成功编译。哥德宝
  • ...发出Clang 8.0.0的编译器错误(代码段下方的错误)。哥德宝

问题:这是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){});
}
Run Code Online (Sandbox Code Playgroud)

令我感到奇怪的是,Clang的错误表明它成功地别名Tuple为,std::tuple<int, int, int>但错误地别名Function为just 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){});
Run Code Online (Sandbox Code Playgroud)

附加研究

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

template <typename T>
using Int = std::conditional_t<true, int, T>;
Run Code Online (Sandbox Code Playgroud)

我发现的其他内容,仅从Function外部引用类型也使它按预期/期望的方式工作:

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

cpp*_*ner 3

TL;DR:这是一个 bug,但标准中也存在一个 bug。

\n\n

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

\n\n
    \n
  1. 定义封闭模板时,会构建不依赖于模板参数的构造。
  2. \n
  3. 依赖的构造是在实例化封闭模板时构建的。
  4. \n
\n\n

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

\n\n
    \n
  1. Int<Ts>是非依赖类型(正确)。
  2. \n
  3. Int<Ts>因此,包含(即)的包扩展Int<Ts>...也是一种非依赖“类型”(?)。
  4. \n
  5. 因为 的所有组件void(Int<Ts>...)都是非依赖的,所以它是非依赖类型(显然是不正确的)。
  6. \n
  7. 因为名称std::function是非依赖的,并且模板参数void(Int<Ts>...)是非依赖的非包扩展类型,std::function< void(Int<Ts>...) >所以是非依赖类型。
  8. \n
\n\n

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

\n\n

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

\n\n

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

\n\n
\n\n

现在这是标准中的错误。

\n\n

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

\n\n
\n

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

\n\n
    \n
  • 模板参数,
  • \n
  • 未知专业的成员,
  • \n
  • 作为当前实例化的依赖成员的嵌套类或枚举,
  • \n
  • 一个 cv 限定类型,其中 cv 不限定类型是从属的,
  • \n
  • 由任何依赖类型构造的复合类型,
  • \n
  • 其元素类型是相关的或其边界(如果有)是值相关的数组类型,
  • \n
  • 其异常规范与值相关的函数类型,
  • \n
  • simple-template-id表示,其中模板名称是模板参数,或者任何模板参数是依赖类型或依赖于类型或值依赖的表达式,或者是包扩展 [\ xe2\x80\x89注意:这包括不使用template-argument-list的\n 类模板的注入类名。\xe2\x80\x94\xe2\x80\x89 尾注\xe2\x80\x89] ,\n 或
  • \n
  • 用 表示decltype(expression),其中表达式与类型相关。
  • \n
\n
\n\n

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

\n