【问题标题】:Return type deduction: What method is preferred?返回类型扣除:首选什么方法?
【发布时间】:2013-03-07 01:27:55
【问题描述】:

我有一个类 Foo,它有两个 A 和 B 类型的任意数据成员。对 Foo::operator()(Arg &&) 的调用将参数转发给这两个成员并返回结果的总和。我可以看到几种方法来实现所有必要的类型推导。是否有一些首选方法可以减少编译器的压力?我的意思是编译时意义上的“应变”,如果嵌套太深会达到内部限制等。你能概括一下,还是它高度特定于给定的编译器?

我可以做“天真的”自动 decltype 变体:

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(Arg && arg) -> decltype(m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg)))
  {
    return m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg));
  }
private:
  A m_a;
  B m_b;
};

我可以编写一个辅助结构,它只作用于类型而不是“真实”实例,而是作用于 std::declval 创建的实例

template <typename A, typename B, typename Arg>
struct Foo_Returns
{
  typedef decltype(std::declval<A>()(std::declval<Arg>()) +
                   std::declval<B>()(std::declval<Arg>())) type;
}

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  typename Foo_Returns<A, B, Arg>::type
  operator()(Arg && arg)
  {
    return m_a(std::forward<Arg>(arg)) + m_b(std::forward<Arg>(arg));
  }
private:
  A m_a;
  B m_b;
};

还有更多的可能吗?

现在让它变得更难:我们有两个特征 is_green 和 is_blue。如果 Arg 为绿色,Foo 的 operator() 应将 Arg 转发给 A 和 B 的成员函数绿色并返回结果之和,类似于 Arg 为蓝色。一种类型永远不会同时是绿色和蓝色。应该可以添加其他类型的风格(因此不允许使用 bool 值来指示蓝色或绿色)。

一种变体会尽可能使用标签调度和 auto -> decltype(...):

struct green_tag { };
struct blue_tag { };
struct error_tag;

template <typename T>
struct category
{
  typedef typename std::conditional<is_green<T>::value, 
                                    green_tag,
                                    typename std::conditional<is_blue<T>::value,
                                                              blue_tag,
                                                              error_tag
                                                             >::type
                                   >::type type;
}

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(Arg && arg) -> decltype(impl(std::forward<Arg>(arg), typename category<Arg>::type()))
  {
    return impl(std::forward<Arg>(arg), typename category<Arg>::type());
  }

private:
  template <typename Arg>
  auto impl(Arg && arg, green_tag) -> decltype(m_a.green(std::forward<Arg>(arg)) + m_b.green(std::forward<Arg>(arg)))
  {
    return m_a.green(std::forward<Arg>(arg)) + m_b.green(std::forward<Arg>(arg));
  }

  template <typename Arg>
  auto impl(Arg && arg, blue_tag) -> decltype(m_a.blue(std::forward<Arg>(arg)) + m_b.blue(std::forward<Arg>(arg)))
  {
    return m_a.blue(std::forward<Arg>(arg)) + m_b.blue(std::forward<Arg>(arg));
  }

  A m_a;
  B m_b;
};

另一个版本可以使用辅助结构:

template <typename A, typename B, typename Arg, typename Category = typename category<Arg>::type>
struct Foo_Returns;

template <typename A, typename B, typename Arg>
struct Foo_Returns<A, B, Arg, green_tag>
{
  typedef decltype(std::declval<A>().green(std::declval<Arg>()) +
                   std::declval<B>().green(std::declval<Arg>())) type;

  type operator()(A & a, B & b, Arg && arg) const
  {
    return a.green(std::forward<Arg>(arg)) + b.green(std::forward<Arg>(arg));
  }  
};

template <typename A, typename B, typename Arg>
struct Foo_Returns<A, B, Arg, blue_tag>
{
  typedef decltype(std::declval<A>().blue(std::declval<Arg>()) +
                   std::declval<B>().blue(std::declval<Arg>())) type;

  type operator()(A & a, B & b, Arg && arg) const
  {
    return a.blue(std::forward<Arg>(arg)) + b.blue(std::forward<Arg>(arg));
  }  
};

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  typename Foo_Returns<A, B, Arg>::type
  operator()(Arg && arg)
  {
    return Foo_Returns<A, B, Arg>()(m_a, m_b, std::forward<Arg>(arg));
  }

private:
  A m_a;
  B m_b;
};

有更好的版本吗?还有哪些可能的方法?

【问题讨论】:

  • gcc-4.8 与 -std=c++1y 和只是 auto 作为返回类型。 ;)
  • 我还没有读完你写的所有东西。但是当你使用成员时,它也适用于你的成员函数是 const (成员将被视为 const)。然后你需要在函数之前声明成员。就像在你的代码中一样,它不起作用。

标签: c++ templates c++11


【解决方案1】:

我会避免使用所有辅助类/结构。每个助手都需要编译器将其存储在某个地方并进行额外的查找。没有这些类,编译器至少有机会优化事物,但我无法想象帮助类可以改善您展示的示例中的情况。

对于您的绿色/蓝色示例,我什至会考虑使用 SFINAE 来缩短代码并减少实例化类/方法的数量:

template <typename A, typename B>
class Foo
{
public:
  Foo(A a, B b) : m_a(std::move(a)), m_b(std::move(b)) { }

  template <typename Arg>
  auto operator()(const Arg & arg) ->
    typename std::enable_if< is_green<Arg>::value,
      decltype(m_a.green(arg) + m_b.green(arg) >::type
  {
    return m_a.green(arg) + m_b.green(arg);
  }

  template <typename Arg>
  auto operator()(const Arg & arg) ->
    typename std::enable_if< is_blue<Arg>::value,
      decltype(m_a.blue(arg) + m_b.blue(arg)) >::type
  {
    return m_a.blue(arg) + m_b.blue(arg);
  }

private:
  A m_a;
  B m_b;
};

我还认为这更易于维护,YMMV。对于编译时性能,一如既往,只有一个真正的建议:衡量它。

编辑:我将参数从 Arg&amp;&amp; 更改为 const Arg&amp; 并删除了双 std::forward&lt;Arg&gt;(...),因为这是非法的,请参阅评论。

【讨论】:

  • arg 被转发两次,一次为m_a,另一次为m_b。
  • @balki:很好的观察,但这是 OP 代码的问题。我会编辑我的答案,谢谢!
猜你喜欢
  • 1970-01-01
  • 2020-02-19
  • 1970-01-01
  • 1970-01-01
  • 2018-03-11
  • 2010-10-10
  • 2015-12-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多