2015-03-24 2 views
4

Я хотел бы написать класс wrapper (очень прокси), который объединяет объект и переадресовывает вызовы функций-членов. Это тривиально в C++ 11/14 с использованием вариативных шаблонов и decltype. Моя проблема заключается в том, что существуют функции-члены, которые могут или не могут поддерживаться обернутым объектом.C++ 11/14: Оберните функцию, если она существует

Я придумал решение, которое, похоже, работает, однако оно выглядит очень неуклюжим, и я ищу упрощения. В частности, я боюсь, что это может быть очень дорогостоящим во время компиляции (есть много функций). Эта неуклюжесть возникает из-за необходимости указывать тип возвращаемой функции, не оставляя при этом 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); 
} 
+0

Почему бы не использовать 'decltype (auto)'? – Jamboree

+0

@Jamboree У вас будет живой пример, чтобы показать, что вы имеете в виду и как он работает? – akim

+0

@akim 'auto' следует правилам вычета аргументов шаблона. 'decltype (auto)' следует правилам decltype. –

ответ

2

Так что я взял кучу вашей работы, которую вы делаете в макросе, и вытащил ее.

can_apply_t принимает template<class...>class и набор типов и является истинным, если типы могут применяться к шаблону на законных основаниях.

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...) и говорит о типе возвращаемого Object.Method(Args...) в SFINAE дружной контексте.

Далее мы создаем шаблон 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. Здесь мы recouple все обратно на одно имя:

#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; } 

, который позволяет получить доступ к Base в wrapper->foo(whatever). Он действительно обнажает все около Base.

Смежные вопросы