我一直在努力解决编译问题,并且能够将问题缩小到一个小代码段。
要设置阶段,我正在尝试执行CRTP,其中基本方法在派生类中调用另一个。复杂的是,我想使用尾随返回类型直接获取转发类型到Derived类的方法。这总是无法编译,除非我转发到派生类中的调用运算符。
编译:
#include <utility>
struct Incomplete;
template <typename Blah>
struct Base
{
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Blah&>()(std::declval<Args&&>()...));
};
void example()
{
Base<Incomplete> derived;
}
虽然这不是:(注意唯一区别的评论)
#include <utility>
struct Incomplete;
template <typename Blah>
struct Base
{
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...));
// I only added this ^^^^^^^^^^^
};
void example()
{
Base<Incomplete> derived;
}
我得到的错误:
<source>: In instantiation of 'struct Base<Incomplete>':
15 : <source>:15:22: required from here
10 : <source>:10:58: error: invalid use of incomplete type 'struct Incomplete'
-> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
在Derived类中解析decltype期间似乎有一些特殊行为。标准中有什么可以解释这个吗?
编辑:进行更大的简化
PS:关于godbolt的编译示例:https://godbolt.org/g/St2gYC
答案 0 :(得分:4)
实例化类模板实例化其成员函数模板([temp.inst]/2)的声明。即我们正在查看声明
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...));
现在考虑[temp.res]/10:
如果名称不依赖于模板参数(如14.6.2中所定义),则声明(或声明集) 该名称应在名称出现在模板定义中的范围内;
实际上,名称operator()
不依赖于模板参数。它既不依赖于类型也不依赖于价值,也不是依赖名称。显然,范围内没有声明,因此声明格式不正确,无需诊断。
相比之下,您的第一个代码段无需在Incomplete
中查找名称。将x(...)
类型x
转换为x.operator()(...)
的转换只有在x [over.call]内查找operator()
后才会发生:
因此,调用
x(arg1,...)
被解释为x.operator()(arg1,...) 如果存在T::operator()(T1, T2, T3)
,则类型为T 的类对象x 如果操作员被过载选为最佳匹配函数 解决机制([over.match.best])。
这与使你的第二个代码格式错误的段落有所不同:[temp.res] / 10表示某些声明必须在范围内,并且名称是绑定的那些宣言。上述转换要求参数类型(以及数字......)是已知的,以便我们可以唯一地确定要调用的一个operator()
;也就是说,我们不只是插入.operator()
,而是始终同时识别调用哪个运算符函数。我们可以在[temp.dep]中找到对此解释的进一步确认:
如果运算符的操作数是类型相关的表达式,则运算符也表示从属名称。这些名称是未绑定的,并且在模板实例化[...]
时查找
operator()
的参数操作数显然与类型有关。