【问题标题】:Why does auto return type change the overload resolution?为什么自动返回类型会改变重载分辨率?
【发布时间】:2014-12-29 21:15:59
【问题描述】:

感谢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 不正确,同意,但它有助于理解 derivedbase 的一种,尽管不是通过继承)。

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)...);
  }
};

不等于这个(问题不在于autodecltype(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&amp;,所以非常热切的decorator::fun 开始了(我们知道Args&amp;&amp;...非常不耐烦地绑定到任何不完全匹配的东西)。所以它将这个转发给无法处理foo_tbase::fun

如果我将derived::fun 更改为foo_t 而不是const foo_t&amp;,那么它会按预期工作,这表明确实这里的问题是derived::fundecorator::fun 之间存在竞争。

但是为什么这会显示返回类型的扣除???更准确地说,为什么委员会选择了这种行为?

为了让事情变得更简单,在 Coliru 上:

谢谢!

【问题讨论】:

  • @Nawaz:感谢您的提示。在这里,没有区别。
  • decltype(b.fun(std::forward&lt;Args&gt;(args)...)) 在尾随返回类型中用作 Expression SFINAE,而 autodecltype(auto) 都不是
  • 添加到 Piotr S. 的评论中:这也是故意的。要使 SFINAE 工作,返回类型必须在不查看函数体的情况下可用。要启用返回 lambda,返回类型必须在不查看函数体的情况下才可用。决定启用返回 lambda 比 SFINAE 更重要。
  • 同意。我刚刚阅读了my own answer 并从中学习。 :|

标签: c++ auto c++14 return-type-deduction


【解决方案1】:

看看这个调用:

d.fun(foo_t{});

您创建一个临时(即右值),将其作为参数传递给函数。现在你认为会发生什么?

  • 它首先尝试绑定到参数Arg&amp;&amp;,因为它可以接受右值但是由于无效的返回类型推导(再次由于foo_t 无法转换为unsigned int,因此b.fun(std::forward&lt;Args&gt;(args)...) 是无效表达式),如果您使用decltype(expr) 作为返回类型,则此函数将被拒绝,如本例中的SFINAE进入画面。但是如果你只使用auto,那么SFINAE就不会出现,并且该错误被归类为导致编译失败的硬错误。

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

【讨论】:

  • 啊,我没有意识到 C++11 可以工作,因为对 base::fun 的调用被拒绝了,then derived::fun 被作为第二选择。非常感谢!但是,decltype(auto) 怎么没有解决问题?是否应该将其视为 GCC 和 Clang 中的错误?
  • 好的,我刚刚阅读了您的问题本身的 cmets,这让一切都清楚了,非常感谢!
猜你喜欢
  • 1970-01-01
  • 2015-10-22
  • 1970-01-01
  • 2015-06-10
  • 1970-01-01
  • 2016-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多