不完整类型的调用操作符的decltype的特殊行为

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

Col*_*mbo 5

实例化类模板会实例化其成员函数模板的声明 ( [temp.inst]/2 )。即我们正在查看声明

\n\n
template <typename... Args>\nauto entry(Args&&... args)\n    -> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...));\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在考虑[temp.res]/10

\n\n
\n

如果名称不依赖于模板参数(如 14.6.2 中定义),则该名称的声明(或声明集)\n 应位于该名称出现在模板定义中的范围内;

\n
\n\n

事实上,该名称operator()不依赖于模板参数。它既不依赖于类型也不依赖于值,也不是依赖名称。显然,范围内没有声明,因此声明格式不正确,不需要诊断。

\n\n

相比之下,您的第一个代码片段不需要在Incomplete. 的转换x(...),其中x是类类型,仅在 x 内查找x.operator()(...)之后发生- [over.call]operator()

\n\n
\n

因此,x(arg1,...)对于 T 类型的类对象 x(如果T\xe2\x80\x8b::\xe2\x80\x8boperator()(T1, T2, T3)存在)且重载决策机制将运算符选为最佳匹配函数,则调用被解释为 x.operator() (arg1, ...) ([超过匹配最佳])。

\n
\n\n

这与使第二个代码格式错误的段落不同:[temp.res]/10 表示某些声明必须在范围内,并且名称绑定到这些声明。上述转换要求参数类型(以及数字……)已知,以便我们可以唯一确定operator()要调用的参数类型;也就是说,我们不只是插入.operator(),而且总是同时识别调用哪个运算符函数。我们可以在 [temp.dep] 中找到对此解释的进一步确认:

\n\n
\n

如果运算符的操作数是依赖于类型的表达式,则该运算符还表示依赖名称。这些名称是未绑定的,并在模板实例化时查找[...]

\n
\n\n

operator()\ 的参数操作数显然与类型相关。

\n