感谢 decltype
作为返回类型,C++11 使得引入装饰器变得非常容易。例如,考虑这个类:
struct base
{
void fun(unsigned) {}
};
我想用额外的功能来装饰它,因为我会用不同种类的装饰做几次,我首先介绍一个 decorator
类,它简单地将所有内容转发到 base
。在实际代码中,这是通过 std::shared_ptr
完成的,这样我就可以移除装饰并恢复“裸”对象,并且一切都是模板化的。
#include <utility> // std::forward
struct decorator
{
base b;
template <typename... Args>
auto
fun(Args&&... args)
-> decltype(b.fun(std::forward<Args>(args)...))
{
return b.fun(std::forward<Args>(args)...);
}
};
完美转发和 decltype
真是太棒了。在实际代码中,我实际上使用了一个只需要函数名称的宏,其余的都是样板。
然后,我可以引入一个 derived
类,为我的对象添加功能(derived
是不恰当的,同意,但它有助于理解 derived
是一种 base
,虽然不是通过继承)。
struct foo_t {};
struct derived : decorator
{
using decorator::fun; // I want "native" fun, and decorated fun.
void fun(const foo_t&) {}
};
int main()
{
derived d;
d.fun(foo_t{});
}
然后C++14来了,带有返回类型推导,允许用更简单的方式写东西:去掉转发函数的decltype
部分:
struct decorator
{
base b;
template <typename... Args>
auto
fun(Args&&... args)
{
return b.fun(std::forward<Args>(args)...);
}
};
然后然后它坏了。是的,至少根据 GCC 和 Clang,这是:
template <typename... Args>
auto
fun(Args&&... args)
-> decltype(b.fun(std::forward<Args>(args)...))
{
return b.fun(std::forward<Args>(args)...);
}
};
不等同于此(问题不在于 auto
与 decltype(auto)
):
template <typename... Args>
auto
fun(Args&&... args)
{
return b.fun(std::forward<Args>(args)...);
}
};
重载决议似乎完全不同,它的结尾是这样的:
clang++-mp-3.5 -std=c++1y main.cc
main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int'
return b.fun(std::forward<Args>(args)...);
^~~~~~~~~~~~~~~~~~~~~~~~
main.cc:32:5: note: in instantiation of function template specialization
'decorator::fun<foo_t>' requested here
d.fun(foo_t{});
^
main.cc:7:20: note: passing argument to parameter here
void fun(unsigned) {}
^
我理解失败:我的调用 (d.fun(foo_t{})
) 与 derived::fun
的签名不完全匹配,它需要一个const foo_t&
,所以非常急切的 decorator::fun
开始发挥作用(我们知道 Args&&...
是如何非常不耐烦地绑定(bind)到任何不完全匹配)。所以它把它转发给不能处理 foo_t
的 base::fun
。
如果我将 derived::fun
更改为采用 foo_t
而不是 const foo_t&
,那么它会按预期工作,这表明确实这里的问题是 derived::fun
和 decorator::fun
之间存在竞争。
但是为什么这会显示返回类型的扣除???更准确地说,为什么委员会选择了这种行为?
为了让事情变得更简单,在 Coliru 上:
谢谢!
最佳答案
看看这个调用:
d.fun(foo_t{});
您创建一个临时(即右值),将其作为参数传递给函数。现在你认为会发生什么?
它首先尝试绑定(bind)到参数
Arg&&
,因为它可以接受右值 但是 由于无效的返回类型推导(这又是由于foo_t
无法转换为unsigned int
,因此b.fun(std::forward<Args>(args)...)
原来是无效的表达式),如果您使用decltype(expr)
,此功能将被拒绝作为返回类型,在这种情况下 SFINAE 就出现了。但是如果你简单地使用auto
, 则 SFINAE 不显示,该错误被归类为导致编译失败的硬错误。接受
foo_t const&
的第二个重载如果 SFINAE 在第一种情况下工作,则调用 as 参数。
关于c++ - 为什么自动返回类型会改变重载分辨率?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26710461/