【问题标题】:C++11/14: Wrap a function if it existsC++11/14:如果函数存在,则包装函数
【发布时间】:2015-03-24 09:40:25
【问题描述】:

我想写一个wrapper 类(非常像一个代理)来聚合一个对象,并将成员函数调用转发给它。这在 C++11/14 中使用可变参数模板和decltype 是微不足道的。我的问题是包装对象可能支持也可能不支持的成员函数。

我想出了一个似乎可行的解决方案,但是它看起来非常笨拙,我正在寻找简化方法。特别是我担心这在编译时可能会非常昂贵(有 许多 函数要包装)。这种笨拙来自于需要指定函数的返回类型,而不需要 decltype 一些令人窒息的东西。

有人有更好的主意吗?

以下代码也可以使用live

#include <iostream>
#include <utility>

/// Compute the result type of a member function call, or void if invalid.
#define RESULT_OF(Name)                                                 \
  template <typename T>                                                 \
  class result_impl_ ## Name                                            \
  {                                                                     \
  public:                                                               \
    /* Type made public to please Clang 3.7. */                         \
    template <typename C, typename... Args>                             \
      static auto Type(void*)                                           \
      -> decltype(std::declval<C>().Name(std::declval<Args>()...));     \
                                                                        \
    template <typename, typename...>                                    \
    static void Type(...);                                              \
                                                                        \
    template <typename... Args>                                         \
      using type = decltype(Type<T, Args...>(0));                       \
  };                                                                    \
                                                                        \
  template <typename T, typename... Args>                               \
  using maybe_result_of_ ## Name                                        \
    = typename result_impl_ ## Name<T>::template type<Args...>

/// Forward to function Name, if is exists.
#define FORWARD(Name)                                                   \
  template <typename... Args>                                           \
  auto Name(Args&&... args)                                             \
    -> maybe_result_of_ ## Name<Base, Args...>                          \
  {                                                                     \
    return base.Name(std::forward<Args>(args)...);                      \
  }

#define DEFINE(Name)                            \
  RESULT_OF(Name);                              \
  FORWARD(Name)


template <typename Base>
struct wrapper
{
  Base base;
  DEFINE(foo);
  DEFINE(bar);
};


#define PING()                                  \
  std::cerr << __PRETTY_FUNCTION__ << '\n'
struct foo_no_bar
{
  void foo(int)           const { PING(); }
  int foo(double)         const { PING(); return 1; }
  int foo(double, double) const { PING(); return 1; }
};

struct foo_and_bar
{
  void foo() const { PING(); }
  void bar()       { PING(); }
};

int main()
{
  wrapper<foo_and_bar> f;
  f.foo();
  f.bar();
  wrapper<foo_no_bar> b;
  b.foo(1);
  b.foo(1.0);
  b.foo(1.0, 2.0);
}

【问题讨论】:

  • 为什么不使用decltype(auto)
  • @Jamboree 你能举个例子来说明你的意思和它是如何工作的吗?
  • @akim auto 遵循模板参数推导规则。 decltype(auto) 遵循 decltype 规则。
  • 如果您在从包装器调用成员函数时使用operator-&gt; 而不是. 表示法很舒服,那么实现起来非常简单。你会对这样的解决方案感兴趣吗?
  • @remyabel 是的,确实,我知道这一点。但是 autodecltype(auto) 对 SFINAE 不友好,所以我看不出它是如何工作的。你有具体的例子吗?

标签: c++ c++11 c++14 enable-if


【解决方案1】:

所以我把你在宏中所做的一堆工作取出来了。

can_apply_t 接受template&lt;class...&gt;class 和一组类型,如果这些类型可以合法地应用于模板,则为 true。

template<class...>struct voider { using type=void; };
template<class...Ts>using void_t=typename voider<Ts...>::type;

template<class...>struct types{ using type=types; };

namespace details {
  template<template<class...>class Z, class types, class=void>
  struct can_apply : std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, types<Ts...>, void_t< Z<Ts...> > >:
    std::true_type
  {};
}

template<template<class...>class Z, class...Ts>
using can_apply_t = details::can_apply<Z, types<Ts...>>;

然后我们会替换您的宏。我将方法名称与对象名称分离,并分几步完成。这些步骤可以在每个方法的类外部完成:

#define CALL_METHOD_RESULT(Name, Method) \
template<class Sig, class=void> \
struct Name {}; \
template<class C, class...Ts> \
struct Name< C(Ts...), void_t< \
  decltype( std::declval<C>().Method(std::declval<Ts>()...) ) \
>> { \
  using type = decltype( std::declval<C>().Method(std::declval<Ts>()...) ); \
}; \
template<class Sig> \
using Name ## _t = typename Name< Sig >::type

上面定义了Name_t,这是一个接受Object(Args...)的特征类,并在SFINAE友好的上下文中告诉你Object.Method(Args...)的返回类型。

接下来,我们使用这个和上面的can_apply_t 构建一个can_call_Method 帮助器模板:

#define CAN_CALL_METHOD( Method ) \
CALL_METHOD_RESULT( call_ ## Method, Method ); \
template<class Sig> \
using can_call_ ## Method = can_apply_t< call_ ## Method ## _t, Sig >

接下来,一个FORWARD 宏为Name 生成一对重载,其中一个调用target.Method(Args...) 其中Target target,而另一个仅在第一个不是且显式=delete 时才考虑s 调用以生成可能更好的错误消息。

/// Forward to function Name, if is exists.
#define FORWARD(Name, Method, Target, target) \
  template <class... Args> \
  auto Name(Args&&... args) \
  -> call_ ## Method ## _t<Target(Args...)> \
  { \
    return target.Method(std::forward<Args>(args)...); \
  } \
  template <class...Args> \
  std::enable_if_t<!can_call_ ## Method <Target(Args...)>{}> \
  Name ( Args&&...) = delete

现在我们将上述内容封装在一个类似的 DEFINE 宏中。在这里,我们将所有内容重新耦合回一个名称:

#define DEFINE(Name) \
  CAN_CALL_METHOD(Name); \
  FORWARD(Name, Name, Base, base)

live example

如果你愿意,我们也可以让=delete 方法做其他事情,比如什么都不做。

但是请注意,如果我们省略整个 =delete 方法,我们实际上可以在两个子对象之间进行受控调度(即使有优先级!)

当然,更简单的方法是

Base* operator->(){ return &base; }  
Base const* operator->()const{ return &base; }  

它允许您以wrapper-&gt;foo(whatever) 的身份访问Base。它确实暴露了关于Base一切

【讨论】:

    猜你喜欢
    • 2016-01-24
    • 1970-01-01
    • 1970-01-01
    • 2018-10-29
    • 1970-01-01
    • 2014-11-26
    • 1970-01-01
    • 2015-10-07
    • 1970-01-01
    相关资源
    最近更新 更多