Ale*_*kiy 7 c++ templates operator-overloading language-lawyer name-lookup
我一直在努力解决编译问题,并且能够将问题缩小到一个小代码段.
为了设置阶段,我正在尝试执行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;
}
Run Code Online (Sandbox Code Playgroud)
虽然这不是:(注意唯一区别的评论)
#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;
}
Run Code Online (Sandbox Code Playgroud)
我得到的错误:
<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&&>()...));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
Run Code Online (Sandbox Code Playgroud)
在Derived类中解析decltype期间似乎存在一些特殊行为.标准中有什么可以解释这个吗?
编辑:进行更大的简化
PS:关于godbolt的编译示例:https://godbolt.org/g/St2gYC
实例化类模板会实例化其成员函数模板的声明 ( [temp.inst]/2 )。即我们正在查看声明
\n\ntemplate <typename... Args>\nauto entry(Args&&... args)\n -> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...));\nRun Code Online (Sandbox Code Playgroud)\n\n现在考虑[temp.res]/10:
\n\n\n\n\n如果名称不依赖于模板参数(如 14.6.2 中定义),则该名称的声明(或声明集)\n 应位于该名称出现在模板定义中的范围内;
\n
事实上,该名称operator()不依赖于模板参数。它既不依赖于类型也不依赖于值,也不是依赖名称。显然,范围内没有声明,因此声明格式不正确,不需要诊断。
相比之下,您的第一个代码片段不需要在Incomplete. 的转换x(...),其中x是类类型,仅在 x 内查找x.operator()(...)之后发生- [over.call]:operator()
\n\n\n因此,
\nx(arg1,...)对于 T 类型的类对象 x(如果T\xe2\x80\x8b::\xe2\x80\x8boperator()(T1, T2, T3)存在)且重载决策机制将运算符选为最佳匹配函数,则调用被解释为 x.operator() (arg1, ...) ([超过匹配最佳])。
这与使第二个代码格式错误的段落不同:[temp.res]/10 表示某些声明必须在范围内,并且名称绑定到这些声明。上述转换要求参数类型(以及数字……)已知,以便我们可以唯一确定operator()要调用的参数类型;也就是说,我们不只是插入.operator(),而且总是同时识别调用哪个运算符函数。我们可以在 [temp.dep] 中找到对此解释的进一步确认:
\n\n\n如果运算符的操作数是依赖于类型的表达式,则该运算符还表示依赖名称。这些名称是未绑定的,并在模板实例化时查找[...]
\n
operator()\ 的参数操作数显然与类型相关。