【问题标题】:Why is this overload of a conversion operator chosen?为什么选择转换运算符的这种重载?
【发布时间】:2020-03-27 06:44:54
【问题描述】:

考虑following code

struct any
{
    template <typename T>
    operator T &&() const;

    template <typename T>
    operator T &() const;
};
int main()
{
    int a = any{};
}

这里的第二个转换运算符是由重载决议选择的。为什么?

据我了解,这两个运算符分别推导出为operator int &amp;&amp;() constoperator int &amp;() const。两者都在一组可行的功能中。通读 [over.match.best] 并没有帮助我弄清楚为什么后者更好。

为什么后者的功能比前者好?

【问题讨论】:

  • 我是一只普通的猫,我会说它必须是第二只猫,否则它的引入将是 C++11 中的重大变化。它会在某个地方成为标准。不过,好问题,请投赞成票。 (注意专家:如果这个评论是废话,请告诉我!)
  • FWIW,template &lt;typename T&gt; operator T &amp;&amp;() const &amp;&amp;; template &lt;typename T&gt; operator T &amp;() const &amp;; 让它调用第一个。
  • @LightnessRaceswithMonica 转换运算符允许这样做,否则将无法拥有多个转换运算符。
  • @Bathsheba 我认为这不是原因。这就像说重载决议永远不能选择移动构造函数,因为这将是一个重大更改。如果您编写移动构造函数,则您选择让您的代码被该定义“破坏”。
  • @LightnessRaceswithMonica 我将其牢记在心的方式是将返回类型视为函数的名称。不同的类型等于不同的名称等于成功:)

标签: c++ language-lawyer c++17 overload-resolution


【解决方案1】:

首选返回T&amp; 的转换运算符,因为它比返回T&amp;&amp; 的转换运算符更专业。

参见 C++17 [temp.deduct.partial]/(3.2):

在调用转换函数的上下文中,使用转换函数模板的返回类型。

和/9:

如果对于给定类型,双向推导成功(即,在上述转换后类型相同)并且PA 都是引用类型(在被上述类型替换之前) : — 如果参数模板中的类型是左值引用,而参数模板中的类型不是,则认为参数类型至少与参数一样特化 类型; ...

【讨论】:

    【解决方案2】:

    推导的返回值转换运算符有点奇怪。但核心思想是,它就像一个函数参数来选择使用哪一个。

    当在T&amp;&amp;T&amp; 之间做出决定时,T&amp; 在重载解决规则中获胜。这是为了允许:

    template<class T>
    void f( T&& ) { std::cout << "rvalue"; }
    template<class T>
    void f( T& ) { std::cout << "lvalue"; }
    

    工作。 T&amp;&amp; 可以匹配左值,但是当左值和通用引用重载都可用时,首选左值。

    正确的转换运算符集可能是:

    template <typename T>
    operator T&&() &&;
    
    template <typename T>
    operator T &() const; // maybe &
    

    甚至

    template <typename T>
    operator T() &&;
    
    template <typename T>
    operator T &() const; // maybe &
    

    为了防止失败的延长寿命咬你。

    3 用于确定排序的类型取决于完成部分排序的上下文:

    [剪辑]

    (3.2) 在调用转换函数的上下文中,使用转换函数模板的返回类型。

    然后在选择重载时取决于“更专业”的规则:

    (9.1) 如果参数模板中的类型是左值引用,而参数模板中的类型不是,则认为参数类型至少与参数类型一样特化;否则,

    因此operator T&amp;&amp; 至少不如operator T&amp; 专业化,同时没有规则状态operator T&amp; 至少不如operator T&amp;&amp; 专业化,所以operator T&amp;operator T&amp;&amp; 更专业化。

    在其他条件相同的情况下,更专业的模板胜过更少的重载解决方案。

    【讨论】:

    • 有人在我回答时添加了language-lawyer 标签;我可以简单地删除。我对阅读的文本有模糊的记忆,该文本指出它被视为参数以选择调用哪个参数,以及讨论&amp;&amp;&amp; 在选择模板重载时如何处理的文本,但梳理出确切的文字需要一段时间。
    【解决方案3】:

    我们正在尝试从 any 初始化 int。它的过程:

    1. 找出我们可以做到的所有方法。也就是说,确定我们所有的候选人。这些来自非显式转换函数,可以通过标准转换序列 ([over.match.conv]) 转换为 int。该部分包含以下短语:

      对返回“对X 的引用”的转换函数的调用是X 类型的glvalue,因此在选择候选函数的过程中,这种转换函数被认为产生X

    2. 选择最佳候选人。

    在第 1 步之后,我们有两个候选人。 operator int&amp;() constoperator int&amp;&amp;() const,为了选择候选函数,两者都被认为产生 int。哪个是产生int最佳 候选人?

    我们确实有一个决胜局,它更喜欢左值引用而不是右值引用 ([over.ics.rank]/3.2.3)。但是,我们并没有真正在此处绑定引用,并且示例有些颠倒 - 这是针对参数是左值与右值引用的情况。

    如果这不适用,那么我们将通过[over.match.best]/2.5 决胜局来选择更专业的函数模板。

    一般来说,经验法则是更具体的转换是最佳匹配。左值引用转换函数比转发引用转换函数更具体,所以首选。我们正在初始化的 int 没有什么需要右值(如果我们改为初始化 int&amp;&amp;,那么 operator T&amp;() const 就不会是候选对象)。

    【讨论】:

    • 3.2.3 实际上说T&amp;&amp; 获胜不是吗?啊,但是我们没有绑定右值。还是我们?在挑选我们的候选人时,某些词一定已经定义了我们如何得到int&amp;int&amp;&amp;,这是一种绑定吗?
    • @Yakk-AdamNevraumont 不,人们更喜欢左值引用而不是右值引用。我认为无论如何这可能是错误的部分,而“更专业”的决胜局是正确的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-04-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多