【问题标题】:Overload resolution and universal reference parameters过载分辨率和通用参考参数
【发布时间】:2014-06-04 16:42:01
【问题描述】:

以下代码可以正常工作,并且可以按预期找到重载:

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T const& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{});
}

但是,如果我将第一个重载替换为“通用参考”版本,则它不再起作用。未找到 NoBuzz 的正确重载。

struct HasBuzz
{
    void buzz() const {}
};

struct NoBuzz {};

template <typename T>
void foo(T&& t)
{
    t.buzz();
}

void foo(NoBuzz const&){}

int main()
{
    foo(HasBuzz{});
    foo(NoBuzz{}); // error: NoBuzz has no member function buzz
}

我该怎么做才能让它发挥作用?

【问题讨论】:

  • 重载分辨率与函数的“胆量”无关。
  • @juanchopanza:啊,是的,我误解了第一种情况下选择重载的原因。但问题仍然存在。
  • void foo(T&amp;&amp; t) 比将临时值绑定到 const 引用 void foo(NoBuzz const&amp;) 更好,因此编译器将使用右值引用函数。我有类似的问题,请参阅stackoverflow.com/questions/24029213/…
  • 我认为您的基本问题的答案是std::enable_if (en.cppreference.com/w/cpp/types/enable_if)。

标签: c++ c++11


【解决方案1】:

简单的解决方案

添加一个可以用 NoBuzz 类型的 rvalue 调用的重载。

void foo(NoBuzz const&){ }; 
void foo(NoBuzz&&)     { }; // overload for rvalues


注意:根据您的实际用例,这可能还不够,因为如果您传递非 const lvalue 类型 NoBuzz em> 到foo 你仍然会实例化模板,因为两个NoBuzz 重载不匹配。

这篇文章的最后是一个更复杂但肯定更简洁的解决方案。


说明

 template<class T>
 void foo (T&&);            // (A)
 void foo (NoBuzz const&);  // (B)

您的 sn-p 的问题在于您的模板 (A) 可以以比重载 (B) 更好的匹配方式实例化。


当编译器发现您尝试使用类型为NoBuzzrvalue 参数调用名为foo 的函数时,它会查找所有名为 的函数>foo 采用NoBuzz 适合的参数。

假设它从您的模板 (A) 开始,在这里它看到 T&amp;&amp; 可推导出为任何引用类型(lvalue rvalue),因为我们传递的是 rvalue T = NoBuzz.

使用T = NoBuzz,实例化的模板在语义上等同于:

void foo (NoBuzz&&); // (C), instantiated overload of template (A)


然后它会继续你的超载(B)。这个重载接受一个 const 左值引用,它可以绑定到左值和右值;但是我们之前的模板实例化(C)只能绑定到rvalues

由于 (C)(B) 更好地匹配,将右值绑定到 T&amp;&amp;U const&amp; 更可取,选择重载并且您得到您在帖子中描述的行为。


高级解决方案

如果传递的类型没有实现.buzz (),我们可以使用一种称为SFINAE 的技术有条件地使模板无法调用。

template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
  return t.buzz();
}

上述解决方案使用了很多C++11的新特性,详细信息请看这里:

【讨论】:

    【解决方案2】:

    refp 的answer 解释了您所看到的行为,并提供了可能的解决方案。另一种选择是确保foo 函数模板不会进入重载决议的候选集,除非T 有一个名为buzz() 的成员函数。

    template <typename T>
    auto foo(T&& t)
        -> decltype((void)(t.buzz()), void())
    {
        t.buzz();
    }
    

    进行此更改后,foo(NoBuzz const&amp;) 重载将在您传递NoBuzz 的实例时被选中。 Live demo

    可以在here 中找到关于尾随返回类型中decltype 表达式的详细说明。我在这里做的唯一不同的事情是代替使用三个子表达式,中间一个是void(),以防止选择用户定义的operator,,我将第一个表达式的结果转换为void ;在这两种情况下,意图和结果是相同的。

    【讨论】:

    • "而不是使用第二个子表达式 void()" 那么你确实使用, void() - 与演员?
    • @dyp :) second subexpression 指的是链接答案中的解释。我猜你对这种情况下的冗余是正确的,因为返回类型也是void,但我将把它留在那里,以便链接的解释仍然有意义。
    猜你喜欢
    • 2012-02-08
    • 2011-03-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    相关资源
    最近更新 更多