【问题标题】:Can you (and should you) disambiguate a function call taking T and const reference to T?你能(而且你应该)消除对 T 和 const 引用的函数调用的歧义吗?
【发布时间】:2020-08-05 20:28:49
【问题描述】:

如果我们有:

void foo(int) {}
void foo(const int&) {}

我们不能像这样调用foo

foo(3);

因为调用不明确:

error: call of overloaded 'foo(int)' is ambiguous
40 |     foo(3);
   |          ^
note: candidate: 'void foo(int)'
36 | void foo(int) {}
   |      ^~~
note: candidate: 'void foo(const int&)'
37 | void foo(const int&) {}
   |      ^~~

我们可以做的是显式提供正确的重载,例如通过函数指针:

auto (*ptr)(const int&) -> void = foo;
ptr(3); // calls the "const int&" overload, obviously

但是,这违背了方便重载的目的。问题是 - 我能以某种方式消除通话歧义吗? 优雅? 方式?是否存在曾经需要为Tconst T&提供两种重载的情况?

【问题讨论】:

  • 使用函数指针将是最干净的解决方案。您可以对 foo 本身进行类型转换,这可行,但可读性不强:static_cast<void (*)(const int&)>(foo)(3);
  • @RemyLebeau 谢谢。然而,我对问题的最后一部分更感兴趣——很抱歉没有足够好地强调这一点。但是在阅读了您的评论后,我开始相信没有理由同时为 T&const T& 提供重载,对吗?
  • “是否存在需要同时为 T 和 const T& 提供重载的情况?” - 你知道,我正要问 那个。因为我还没有遇到需要出现的情况。
  • 不管这个有没有真正的用例,我还是想知道答案。
  • @StoryTeller-UnslanderMonica 这个问题纯粹是学术性的——我觉得仅凭我的意见并不能成为一个足够的答案,因为直到一个小时前我才研究过这个话题。我没有找到任何表明它需要的东西,所以我想与 SO 专家确认这一点。你的怀疑,如果我可以这样称呼的话,是绝对正确的。

标签: c++ overload-resolution


【解决方案1】:

您可以利用模板。重载解析有利于非模板函数而不是模板函数,因此将其中一个重载转换为模板就足以消除歧义:

#include <iostream>

void foo(int n)
{
    std::cout << "By Value  " << n;
}

template<int N = 0>
void foo(const int& n)
{
    std::cout << "By Reference " << n;
}

int main() {
    foo(1);
    foo<>(1);
}

当然,您需要&lt;&gt; 来调用模板之一,但这可能有一些用途。表面上比函数指针更优雅?但遗憾的是,这并没有比将 foo&lt;&gt; 重命名为 bar 更好。

【讨论】:

  • 很有趣,但我不确定它是否通过了优雅测试。
  • @wcochran,拜托,朋友之间的&lt;&gt;是什么?
  • 即使我们删除 const&amp; 部分,这仍然有效,因此,虽然它显示了一个有趣的工具(已投票),但不幸的是,它并没有足够接近地解决问题。
【解决方案2】:

您似乎在问我们是否可以强制重载解决机制选择一个签名而不是另一个签名,而不是明确拼出您想要的签名。

据我所知,强制重载决议选择const int&amp; 重载的int 重载的唯一方法是将参数强制转换为volatile int glvalue,并且没有办法强制重载决议选择const int&amp; 过载而不是int 过载。

无论如何,我想不出有什么理由要编写这组特定的重载。

【讨论】:

    【解决方案3】:

    我们不能这样调用 foo:foo(3); 因为调用不明确

    一般来说,这正是开发人员避免提供 类似的重载的原因。这使得很难使用重载。通常,开发人员将有一个 const T&amp; 和一个 T&amp;&amp; 重载,因为它们永远不会模棱两可。

    但是,这违背了方便重载的目的。

    方便重载的目的是让调用代码轻松调用正确的函数。准确调用一个是实现方便重载的目的。强制转换为函数指针的烦恼是重载过于靠近的副作用,这是不正常的。

    我能想到的唯一例外是,当您想将一组重载的函数作为 functoid 传递给其他东西,并让 调用者选择它想要的重载时。

    struct foo_functoid {
        void operator()(int v) {foo(v);}
        void operator()(const int& v) {foo(v);}
    };
    //or
    struct foo_functoid {
        template<class T>
        void operator()(T&& v) {foo(std::forward<T>(v));}
    };
    

    但不幸的是,对于这些 functoid,没有捷径,它们必须手动显式编写(可能需要宏的辅助)

    【讨论】:

    • "但是,这违背了方便重载的目的。" - 我的意思是参考auto (*ptr)(const int&amp;) -&gt; void = foo; ptr(3); 部分。
    • 我编辑了该部分以更清楚地表明强制转换为指针是重载过于靠近的副作用并且不正常。
    猜你喜欢
    • 1970-01-01
    • 2010-11-17
    • 1970-01-01
    • 1970-01-01
    • 2019-01-01
    • 2023-01-30
    • 2017-04-10
    • 2011-05-30
    • 1970-01-01
    相关资源
    最近更新 更多