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

isa*_*ndi 1 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{});
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我用"通用引用"版本替换第一个重载,那么它就不再起作用了.找不到正确的过载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
}
Run Code Online (Sandbox Code Playgroud)

我能做些什么才能让它发挥作用?

Fil*_*efp 9

简单解决方案

添加一个可以使用NoBuzz类型的右值调用的重载.

void foo(NoBuzz const&){ }; 
void foo(NoBuzz&&)     { }; // overload for rvalues
Run Code Online (Sandbox Code Playgroud)


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

在这篇文章的最后是一个更复杂,但肯定更清洁的解决方案.


说明

 template<class T>
 void foo (T&&);            // (A)
 void foo (NoBuzz const&);  // (B)
Run Code Online (Sandbox Code Playgroud)

你的代码片段的问题在于你的模板(A)可以以比你的重载(B)更好的匹配方式进行实例化.


当编译器发现你试图用一个类型为rvalue的参数调用一个名为foo的函数时,它会查找名为foo的所有函数,这些函数采用一个适合的参数.NoBuzzNoBuzz

让我们说它从你的模板(A)开始,在这里它看到T&&任何引用类型(左值右值)都可以推断,因为我们传递一个右值 T = NoBuzz.

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

void foo (NoBuzz&&); // (C), instantiated overload of template (A)
Run Code Online (Sandbox Code Playgroud)


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

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


高级解决方案

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

template <typename T>
auto foo(T&& t) -> decltype (t.buzz ())
{
  return t.buzz();
}
Run Code Online (Sandbox Code Playgroud)

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