C ++ 11/14: Wrap a function if it exists

I would like to write a class wrapper(very proxy) that aggregates an object and sends it functions of a member function. This is trivial in C ++ 11/14 using variable templates and decltype. My problem is that there are member functions that may or may not be supported by the wrapped object.

I came up with a solution that seems to work, however it looks very awkward and I'm looking for simplifications. In particular, I am afraid that it can be very expensive at compile time (there are many functions for wrapping). This clumsiness arises from the need to specify the return type of the function without leaving something to strangle in decltype.

Will anyone have a better idea?

The following code snippet is also available 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);
}
+4
source share
1 answer

So, I took a bunch of your work that you do in a macro and pulled it out.

can_apply_taccepts template<class...>classboth a package of types and is true if the types can be applied to the template legally.

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

Then we get replacements for your macros. I separate method names from object names and do this in a few steps. These steps can be performed outside the class for each method:

#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

The above example defines Name_twhich is a feature class that accepts Object(Args...)and tells you the return type Object.Method(Args...)in a friendly SFINAE context.

can_call_Method, , can_apply_t:

#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 , , .

/// 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)

=delete - , , , .

, =delete, - ( !)

, ,

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

Base wrapper->foo(whatever). Base.

+2

All Articles