为什么自动返回类型会改变重载分辨率?

aki*_*kim 24 c++ auto return-type-deduction c++14

感谢decltype作为返回类型,C++ 11使得引入装饰器非常容易.例如,考虑这个类:

struct base
{
  void fun(unsigned) {}
};
Run Code Online (Sandbox Code Playgroud)

我想用其他功能来装饰它,因为我会用不同种类的装饰做几次,所以我首先介绍一个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)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

完美的转发,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{});
}
Run Code Online (Sandbox Code Playgroud)

然后C++ 14带来了返回类型推导,它允许以更简单的方式编写内容:删除decltype转发函数的一部分:

struct decorator
{
  base b;

  template <typename... Args>
  auto
  fun(Args&&... args)
  {
    return b.fun(std::forward<Args>(args)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

并且然后它打破.是的,至少根据GCC和Clang的说法,这个:

  template <typename... Args>
  auto
  fun(Args&&... args)
    -> decltype(b.fun(std::forward<Args>(args)...))
  {
    return b.fun(std::forward<Args>(args)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

不等于这个(问题不是autodecltype(auto)):

  template <typename... Args>
  auto
  fun(Args&&... args)
  {
    return b.fun(std::forward<Args>(args)...);
  }
};
Run Code Online (Sandbox Code Playgroud)

重载决议似乎完全不同,它结束如下:

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) {}
                   ^
Run Code Online (Sandbox Code Playgroud)

我理解失败:我的call(d.fun(foo_t{}))与签名完全不匹配derived::fun,这需要a const foo_t&,所以非常渴望decorator::fun踢(我们知道如何Args&&...非常不耐烦地绑定到任何完全不匹配的东西).所以它转发给了无法解决的base::fun问题foo_t.

如果我derived::fun改为采取foo_t代替const foo_t&,那么它按预期工作,这表明确实在这里问题是derived::fun和之间存在竞争decorator::fun.

然而,为什么这个显示与返回类型扣除??? 更确切地说,为什么委员会选择了这种行为?

为了让事情更容易,在Coliru:

谢谢!

Naw*_*waz 18

看看这个电话:

d.fun(foo_t{});
Run Code Online (Sandbox Code Playgroud)

您创建一个临时(即右值),将其作为参数传递给函数.那你觉得怎么样?

  • 它首先尝试绑定到参数Arg&&,因为它可以接受rvalue 由于无效的返回类型推导(由于foo_t无法转换为unsigned int,因此原因b.fun(std::forward<Args>(args)...)是无效表达式),如果使用decltype(expr)as ,则拒绝此函数返回类型,在这种情况下SFINAE进入图片.但是如果你简单地使用auto,那么SFINAE就不会出现,并且错误被归类为硬错误,导致编译失败.

  • foo_t const&如果SFINAE在第一种情况下工作,则调用接受作为参数的第二个重载.