使用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
类型,也可以使其按预期/期望的方式工作:
int main()
{
auto f = Foo<char, bool, double>::Function{};
f = [](int, int, int){};
}
答案 0 :(得分:1)
TL; DR:这是一个叮叮当当的错误,但标准中也存在一个错误。
首先请注意,在C ++中,模板的处理分为两个步骤:
现在,似乎clang将std::function< void(Int<Ts>...) >
视为非依赖类型,其原因如下:
Int<Ts>
是非依赖性类型(正确)。Int<Ts>
(即Int<Ts>...
)的包扩展也是非依赖的“类型”(?)。void(Int<Ts>...)
的所有组件都是非依赖的,因此它是非依赖的类型(显然是错误的)。std::function
是非依赖性的,并且模板参数void(Int<Ts>...)
是非依赖性的非压缩扩展类型,所以std::function< void(Int<Ts>...) >
是非依赖性的类型。(请注意,“ 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)
表示,其中表达式是类型相关的。
请注意,它不是说参数列表包含压缩扩展的函数类型是从属类型,而只是说“由任何从属类型构造的复合类型”和“异常说明与值有关的函数类型”是依赖的。两者都没有帮助。