过载分辨率和通用参考参数

时间:2014-06-04 16:42:01

标签: c++ c++11

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

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
}

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

2 个答案:

答案 0 :(得分:9)

简单解决方案

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

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


注意:根据您的实际使用情况,这可能还不够,因为如果您传递非const 左值类型 NoBuzz foo您仍然会实例化模板,因为两个NoBuzz重载并不匹配。

在这篇文章的最后是一个更复杂但更清晰的解决方案。


<强>解释

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

您的代码段的问题在于,您的模板(A)可以通过这样的方式进行实例化:它比您的重载(B)更好地匹配


当编译器发现您尝试使用类型为NoBuzz rvalue 参数调用名为 foo 的函数时,它将查找名为的所有函数 foo NoBuzz适合的情况下选择一个参数。

让我们说你的模板(A)开始了,在这里它看到T&&可以被任何引用类型(两个 lvalue 和 rvalue ),因为我们传递的是 rvalue T = NoBuzz

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

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


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

由于(C)是比(B)更好的匹配,因此T&&的绑定rvalues优先于U const&,该重载是选中后,您将获得您在帖子中描述的行为。


高级解决方案

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

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

上述解决方案使用 C ++ 11 的许多新功能,详细信息可在此处获取:

答案 1 :(得分:2)

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

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

进行此更改后,当您向其传递foo(NoBuzz const&)的实例时,将选择NoBuzz重载。 Live demo

可以找到尾随返回类型中decltype表达式中发生的内容的详细说明here。我在这里做的唯一不同的是,不是使用三个子表达式,中间的一个是void(),以防止用户定义的operator,被选中,我已经转换了第一个表达式的结果到void;在两种情况下,意图和结果都是相同的。