Clang 无法在 std::function 实例化中扩展参数包
Clang fails to expand parameter pack in std::function instantiation
使用 std=c++17
作为唯一编译器标志编译的代码片段 ...
问题:这是 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
类型也使它像 expected/desired:
一样工作
int main()
{
auto f = Foo<char, bool, double>::Function{};
f = [](int, int, int){};
}
TL;DR:这是一个 clang 错误,但标准中也有一个错误。
首先请注意,在 C++ 中,模板的处理分两步进行:
- 不依赖模板参数的构造在定义封闭模板时构建。
- 依赖的构造在实例化封闭模板时构建。
现在,clang 似乎将 std::function< void(Int<Ts>...) >
视为 non-dependent 类型,推理如下:
Int<Ts>
是 non-dependent 类型(正确)。
- 因此,包含
Int<Ts>
(即 Int<Ts>...
)的扩展包也是 non-dependent "type" (?).
- 因为
void(Int<Ts>...)
的所有分量都是non-dependent,所以是non-dependent类型(显然不正确)。
- 因为名称
std::function
是 non-dependent 并且模板参数 void(Int<Ts>...)
是 non-dependent non-pack-expansion 类型,所以 std::function< void(Int<Ts>...) >
是 non-dependent类型。
(请注意,"non-pack-expansion" 检查与 Tuple
情况不同。)
因此,当定义Foo
时,类型名称Function
被视为命名non-dependent类型,并立即构建,并打包扩展(在实例化期间发生)不计入。因此,Function
的所有使用都替换为 "desugared" 类型 std::function< void(int) >
.
此外,clang有instantiation-dependent的概念,这意味着构造是不依赖的,但它仍然以某种方式涉及模板参数(例如构造只是对某些参数有效)。 std::function< void(Int<Ts>...) >
被视为 instantiation-dependent 类型,因此当模板被实例化时,clang 仍然执行对 using Function = std::function< void(Int<Ts>...) >
的替换。因此,Function
获得了正确的类型,但这不会传播到 Foo
.
定义中对 Function
的使用
现在是标准中的错误。
类型是否依赖定义在[temp.dep.type]:
A type is dependent if it is
- 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.
请注意,它并没有说参数列表包含包扩展的函数类型是依赖类型,只是说 "a compound type constructed from any dependent type" 和 "a function type whose exception specification is value-dependent" 是依赖类型。两者都无济于事。
使用 std=c++17
作为唯一编译器标志编译的代码片段 ...
问题:这是 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
类型也使它像 expected/desired:
int main()
{
auto f = Foo<char, bool, double>::Function{};
f = [](int, int, int){};
}
TL;DR:这是一个 clang 错误,但标准中也有一个错误。
首先请注意,在 C++ 中,模板的处理分两步进行:
- 不依赖模板参数的构造在定义封闭模板时构建。
- 依赖的构造在实例化封闭模板时构建。
现在,clang 似乎将 std::function< void(Int<Ts>...) >
视为 non-dependent 类型,推理如下:
Int<Ts>
是 non-dependent 类型(正确)。- 因此,包含
Int<Ts>
(即Int<Ts>...
)的扩展包也是 non-dependent "type" (?). - 因为
void(Int<Ts>...)
的所有分量都是non-dependent,所以是non-dependent类型(显然不正确)。 - 因为名称
std::function
是 non-dependent 并且模板参数void(Int<Ts>...)
是 non-dependent non-pack-expansion 类型,所以std::function< void(Int<Ts>...) >
是 non-dependent类型。
(请注意,"non-pack-expansion" 检查与 Tuple
情况不同。)
因此,当定义Foo
时,类型名称Function
被视为命名non-dependent类型,并立即构建,并打包扩展(在实例化期间发生)不计入。因此,Function
的所有使用都替换为 "desugared" 类型 std::function< void(int) >
.
此外,clang有instantiation-dependent的概念,这意味着构造是不依赖的,但它仍然以某种方式涉及模板参数(例如构造只是对某些参数有效)。 std::function< void(Int<Ts>...) >
被视为 instantiation-dependent 类型,因此当模板被实例化时,clang 仍然执行对 using Function = std::function< void(Int<Ts>...) >
的替换。因此,Function
获得了正确的类型,但这不会传播到 Foo
.
Function
的使用
现在是标准中的错误。
类型是否依赖定义在[temp.dep.type]:
A type is dependent if it is
- 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.
请注意,它并没有说参数列表包含包扩展的函数类型是依赖类型,只是说 "a compound type constructed from any dependent type" 和 "a function type whose exception specification is value-dependent" 是依赖类型。两者都无济于事。