【问题标题】:Special behavior for decltype of call operator for incomplete types不完整类型的调用运算符 decltype 的特殊行为
【发布时间】:2018-01-13 02:34:09
【问题描述】:

我一直在努力解决编译问题,并且能够将问题缩小到一个小的代码段。

为了做好准备,我正在尝试执行 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

【问题讨论】:

  • 你为什么不写std::declval&lt;Blah&gt;()?我猜第二个块是 Incomplete 的 odr 使用,而第一个不是。
  • @PasserBy 无论哪种方式,都是同样的问题。我将其更改为您的建议,以免造成混乱。
  • 您可能正在寻找这个吗? stackoverflow.com/questions/7943525/…

标签: c++ templates operator-overloading language-lawyer name-lookup


【解决方案1】:

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

template <typename... Args>
auto entry(Args&&... args)
    -> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...));

现在考虑[temp.res]/10

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

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

相比之下,您的第一个 sn-p 不需要在 Incomplete 中查找名称。 x(...)(其中x 属于类类型)到x.operator()(...) 的转换仅发生在operator() 在x 中查找到[over.call] 之后:

因此,调用 x(arg1,...) 被解释为 x.operator()(arg1, ...) 如果T​::​operator()(T1, T2, T3) 存在且类型为 T 的类对象 x 如果运算符被重载选为最佳匹配函数 解决机制([over.match.best])。

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

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

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

【讨论】:

  • 太棒了!完美的解释。作为旁注,我解决此问题的方法是强制 operator() 依赖于模板参数。先引入另一个declvaltemplate &lt;typename T, typename...&gt; T mydeclval();,然后像这样使用decltype(mydeclval&lt;Incomplete, Args...&gt;().operator(std::declval&lt;Args&amp;&amp;&gt;()...))
猜你喜欢
  • 2014-09-23
  • 1970-01-01
  • 1970-01-01
  • 2018-09-12
  • 2017-11-04
  • 2015-08-30
  • 2019-04-21
  • 2022-01-11
  • 1970-01-01
相关资源
最近更新 更多