Оболочка для функционального объекта для изменения типов аргументов и возвращаемых значений - PullRequest
0 голосов
/ 03 мая 2018

Я хочу написать класс C ++ (пусть имя типа будет Adapter), который принимает указатели на функции или функциональные объекты (особенно лямбда-функции) в конструкторе и сохраняет его по значению внутри (как std :: function для exampl). После того, как класс создан, он также представляет функциональный объект и действует как оболочка для внутреннего функционального объекта. Но со следующими условиями:

  1. тип возвращаемого значения для класса-оболочки (Adapter) всегда предопределен (пусть это будет ReturnType);
  2. но внутренний функтор может не иметь возвращаемого значения - в этом случае подставляется некоторое DefaultValue;
  3. класс-оболочка всегда получает некоторый ArgType в качестве аргумента оператора ();
  4. но внутренняя функция может также принимать ArgType в качестве аргумента или может вообще не принимать аргументы - в последнем случае ее просто следует вызывать без аргументов.

Первый вопрос - это вообще возможно с C ++ 11, а второй вопрос, как я могу это сделать? Я понятия не имею, с чего мне начать.

Мне нужно вывести (в шаблоне) возвращаемое значение функционального объекта? Первый недостаток в том, что std :: result_of не поддерживает указатели на функции. И я понятия не имею, как я могу вывести тип аргумента (он может быть пустым, без аргументов или ArgType) для функционального объекта (я знаю, как это возможно для указателей на функции, через шаблонную структуру, но как я могу сделать это для функционального объекта ?).

Мне нужно что-то вроде этого:

template <typename Functor?> class Adapter
{
private:
    std::function<Void_or_ReturnType (Void_or_ArgType)> f;

public:
    template <typename Functor?> Adapter(const Functor& f) { ??? }

    ReturnType operator()(ArgType arg)
    {
         // one of the following four variants:
         return f(arg);
         f(arg); return DefaultValue;
         return f();
         f(); return DefaultValue;
  }
};

Может быть, мне нужно вывести типы Void_or_ReturnType и Void_or_ArgType в шаблон и написать различную специализацию шаблона для operator () для каждого из четырех случаев. Но как именно я могу это сделать?

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Ну ... то, что вы спрашиваете, не совсем тривиально ...

Прежде всего: я не считаю хорошей идеей принимать возвращаемое значение по умолчанию в качестве параметров шаблона: работает для целочисленных типов, но, например, не работает для типов с плавающей запятой. Вы можете обойти эту проблему, но я предлагаю передать значение по умолчанию в качестве значения в конструкторе.

Второе: я предлагаю следующий код, чтобы определить, можно ли вызвать вызываемый объект типа F id с данным списком типов

template <typename ...>
constexpr std::false_type isInvocableWithHelper (long);

template <typename F, typename ... Args>
constexpr auto isInvocableWithHelper (int)
   -> decltype( std::declval<F>()
                   (std::forward<Args>(std::declval<Args>())...),
                std::true_type{} );

template <typename F, typename ... Args>
using isInvocableWith = decltype(isInvocableWithHelper<F, Args...>(0));

Теперь, используя делегирующие конструкторы и диспетчеризацию тегов, ваш Adapter конструктор может быть

  template <typename F>
  Adapter (F const & f, RetT defVal = RetT{})
     : Adapter{f, defVal, isInvocableWith<F, Args...>{},
               isInvocableWith<F>{}}
   { }

где третий аргумент (isInvocableWith<F, Args...>{}) является истинным (std::true_type), только если объект F можно вызвать со списком объектов Args..., а четвертый аргумент (isInvocableWith<F>{}) является истинным (std::true_type) *) только если объект F можно вызвать без аргумента.

Теперь вы должны определить, возвращает ли F (объект типа F) void или значение (предположительно RetT или что-то, конвертируемое в RetT).

Вы можете снова использовать делегирование конструкторов и диспетчеризацию тегов с помощью

  template <typename F>
  Adapter (F const & f, RetT const & defVal, std::true_type const &,
           std::false_type const &)
   : Adapter{f, defVal, std::true_type{}, std::false_type{},
             std::is_same<void,
                decltype(f(std::forward<Args>(std::declval<Args>())...))>{}}
   { } 

в случае вызова с Args... аргументами (обратите внимание, что последними аргументами являются std::true_type (наследовать от std::true_type), если f return void).

В случае f, вызываемого без аргументов, конструктор становится

  template <typename F>
  Adapter (F const & f, RetT const & defVal, std::false_type const &,
           std::true_type const &)
   : Adapter{f, defVal, std::false_type{}, std::true_type{},
             std::is_same<void, decltype(f())>{}}
   { }

Теперь четыре финальных конструктора

  template <typename F>
  Adapter (F const & f, RetT const & defVal, std::true_type const &,
           std::false_type const &, std::true_type const &)
     : defV{defVal}
   {
     func = [&, this](Args && ... as)
      { f(std::forward<Args>(as)...); return defV; };

     std::cout << "--- case 1 (full, void)" << std::endl;
   }

  template <typename F>
  Adapter (F const & f, RetT const &, std::true_type const &,
           std::false_type const &, std::false_type const &)
   {
     func = [&](Args && ... as)
      { return f(std::forward<Args>(as)...); };

     std::cout << "--- case 2 (full, RetT)" << std::endl;
   }

  template <typename F>
  Adapter (F const & f, RetT const & defVal, std::false_type const &,
           std::true_type const &, std::true_type const &)
     : defV{defVal}
   {
     func = [&, this](Args && ...)
      { f(); return defV; };

     std::cout << "--- case 3 (noArgs, void)" << std::endl;
   }

  template <typename F>
  Adapter (F const & f, RetT const &, std::false_type const &,
           std::true_type const &, std::false_type const &)
   {
     func = [&](Args && ...)
      { return f(); };

     std::cout << "--- case 4 (noArgs, RetT)" << std::endl;
   }

Обратите внимание, что во всех случаях я создал лямбда-функцию, которая получает список аргументов Args... и использует ее только в случае необходимости.

Я понимаю, что это немного сложно, но следующий пример в вашем ответе изменен в соответствии с этим решением.

#include <cstdio>
#include <iostream>
#include <functional>

template <typename ...>
constexpr std::false_type isInvocableWithHelper (long);

template <typename F, typename ... Args>
constexpr auto isInvocableWithHelper (int)
   -> decltype( std::declval<F>()
                   (std::forward<Args>(std::declval<Args>())...),
                std::true_type{} );

template <typename F, typename ... Args>
using isInvocableWith = decltype(isInvocableWithHelper<F, Args...>(0));

template <typename RetT, typename ... Args>
class Adapter
 {
   private:
      std::function<RetT(Args ...)> func;

      RetT  defV { RetT{} };

      template <typename F>
      Adapter (F const & f, RetT const & defVal, std::true_type const &,
               std::false_type const &, std::true_type const &)
         : defV{defVal}
       {
         func = [&, this](Args && ... as)
          { f(std::forward<Args>(as)...); return defV; };

         std::cout << "--- case 1 (full, void)" << std::endl;
       }

      template <typename F>
      Adapter (F const & f, RetT const &, std::true_type const &,
               std::false_type const &, std::false_type const &)
       {
         func = [&](Args && ... as)
          { return f(std::forward<Args>(as)...); };

         std::cout << "--- case 2 (full, RetT)" << std::endl;
       }

      template <typename F>
      Adapter (F const & f, RetT const & defVal, std::false_type const &,
               std::true_type const &, std::true_type const &)
         : defV{defVal}
       {
         func = [&, this](Args && ...)
          { f(); return defV; };

         std::cout << "--- case 3 (noArgs, void)" << std::endl;
       }

      template <typename F>
      Adapter (F const & f, RetT const &, std::false_type const &,
               std::true_type const &, std::false_type const &)
       {
         func = [&](Args && ...)
          { return f(); };

         std::cout << "--- case 4 (noArgs, RetT)" << std::endl;
       }

      template <typename F>
      Adapter (F const & f, RetT const & defVal, std::true_type const &,
               std::false_type const &)
       : Adapter{f, defVal, std::true_type{}, std::false_type{},
                 std::is_same<void,
                    decltype(f(std::forward<Args>(std::declval<Args>())...))>{}}
       { } 

      template <typename F>
      Adapter (F const & f, RetT const & defVal, std::false_type const &,
               std::true_type const &)
       : Adapter{f, defVal, std::false_type{}, std::true_type{},
                 std::is_same<void, decltype(f())>{}}
       { }

   public:
      template <typename F>
      Adapter (F const & f, RetT defVal = RetT{})
         : Adapter{f, defVal, isInvocableWith<F, Args...>{},
                   isInvocableWith<F>{}}
       { }

      template <typename ... As>
      RetT operator() (As && ... as) const
       { return func(std::forward<As>(as)...); }
 };

void deaf_silent_f ()
 { puts("deaf/silent"); }

void silent_f (char const * arg)
 { printf("silent %s\n", arg); }

char const * deaf_f ()
 { puts("deaf"); return "deaf"; }

char const * normal_f (char const * arg)
 { printf("normal %s\n", arg); return "normal"; }

int main ()
 {    
   typedef Adapter<char const *, char const *> A;

    {
      puts("function refs");
      A ds(deaf_silent_f, "Def1"); printf("-> %s\n", ds("ds"));
      A s(silent_f, "Def2"); printf("-> %s\n", s("s"));
      A d(deaf_f); printf("-> %s\n", d("d"));
      A n(normal_f); printf("-> %s\n", n("n"));
      puts("");
    }

    {
      puts("function pointers");
      A ds(&deaf_silent_f, "Def3"); printf("-> %s\n", ds("ds"));
      A s(&silent_f, "Def4"); printf("-> %s\n", s("s"));
      A d(&deaf_f); printf("-> %s\n", d("d"));
      A n(&normal_f); printf("-> %s\n", n("n"));
      puts("");
    }

    {
      puts("functors");
      A ds([=]{ printf("deaf/silent %d\n", 42); }, "Def5");
      printf("-> %s\n", ds("ds"));
      A s([=](char const * a){ printf("silent(%s) %d\n", a, 42); }, "Def6");
      printf("-> %s\n", s("s"));
      A d([=]{ printf("deaf %d\n", 42); return "deaf"; });
      printf("-> %s\n", d("d"));
      A n([=](char const * a){ printf("normal(%s) %d\n", a, 42);
                               return "normal";});
      printf("-> %s\n", n("n"));
    }
 }
0 голосов
/ 04 мая 2018

Я потратил несколько часов на это и нашел какое-то решение: http://coliru.stacked -crooked.com / а / 628b8971466fa74d

Не уверен, если это хорошее решение. Может быть, кто-то предложит мне, как я могу минимизировать этот код.

Я на всякий случай копирую исходный код (я обновил исходники на coliru.stacked-crooked.com, здесь старая версия, см. Ссылку выше):

#include <functional>
#include <stdio.h>

// functors
template<class T, class Yes, class No> struct IsArgs   { typedef typename IsArgs  <decltype(&T::operator()), Yes, No>::type type; };
template<class T, class Yes, class No> struct IsResult { typedef typename IsResult<decltype(&T::operator()), Yes, No>::type type; };

// function pointers
template<class R, class Yes, class No, typename ...Args> struct IsArgs   <R (*)(Args...), Yes, No>    { typedef Yes type; };
template<class R, class Yes, class No>                   struct IsArgs   <R (*)(), Yes, No>           { typedef No  type; };
template<class Yes, class No, class R, typename ...Args> struct IsResult <R (*)(Args...), Yes, No>    { typedef Yes type; };
template<class Yes, class No, typename ...Args>          struct IsResult <void (*)(Args...), Yes, No> { typedef No  type; };

// for function refs
template<class R, class Yes, class No, typename ...Args>    struct IsArgs  <R (Args...), Yes, No>     { typedef Yes type; };
template<class R, class Yes, class No>                      struct IsArgs  <R (), Yes, No>            { typedef No  type; };
template<class Yes, class No, typename R, typename ...Args> struct IsResult <R (Args...), Yes, No>    { typedef Yes type; };
template<class Yes, class No, typename ...Args>             struct IsResult <void (Args...), Yes, No> { typedef No  type; };

// for member pointers (lambdas, functors)
template<class T, class Yes, class No, class R, typename ...Args> struct IsArgs   <R (T::*)(Args...) const, Yes, No>    { typedef Yes type; };
template<class T, class Yes, class No, class R>                   struct IsArgs   <R (T::*)() const, Yes, No>           { typedef No  type; };
template<class T, class Yes, class No, class R, typename ...Args> struct IsResult <R (T::*)(Args...) const, Yes, No>    { typedef Yes type; };
template<class T, class Yes, class No, typename ...Args>          struct IsResult <void (T::*)(Args...) const, Yes, No> { typedef No  type; };

template <typename Retval, Retval Default, typename ...Args> class Adapter
{
    struct Noargs {
        std::function<Retval ()> func;
        template <typename F> Noargs(const F& f) : func(f) {}
        Retval operator()(Args...) { return func(); }
    };

    struct Noreturn {
        std::function<void (Args...)> func;
        template <typename F> Noreturn(const F& f) : func(f) {}
        Retval operator()(Args...args) { return func(args...), Default; }
    };

    struct Noretargs {
        std::function<void ()> func;
        template <typename F> Noretargs(const F& f) : func(f) {}
        Retval operator()(Args...) { return func(), Default; }
    };

    std::function<Retval (Args...args)> func;

public:
    template <typename Functor> Adapter(const Functor &f)
        : func(typename IsArgs<Functor,
                typename IsResult<Functor, std::function<Retval (Args...)>, Noreturn>::type, 
                typename IsResult<Functor, Noargs, Noretargs>::type>::type(f)) {}

    Retval operator()(Args...args) const { return func(args...); }
};




void deaf_silent_f() { puts("deaf/silent"); }
void silent_f(const char *arg) { printf("silent %s\n", arg); }
const char *deaf_f() { puts("deaf"); return "deaf"; }
const char *normal_f(const char *arg) { printf("normal %s\n", arg); return "normal"; }

const char Default[] = "DEFAULT";

int main(int argc, char *argv[])
{    
    typedef Adapter<const char*, Default, const char*> A;

    {
      puts("function refs");
      A ds(deaf_silent_f); printf("-> %s\n", ds("ds"));
      A s(silent_f); printf("-> %s\n", s("s"));
      A d(deaf_f); printf("-> %s\n", d("d"));
      A n(normal_f); printf("-> %s\n", n("n"));
      puts("");
    }

    {
      puts("function pointers");
      A ds(&deaf_silent_f); printf("-> %s\n", ds("ds"));
      A s(&silent_f); printf("-> %s\n", s("s"));
      A d(&deaf_f); printf("-> %s\n", d("d"));
      A n(&normal_f); printf("-> %s\n", n("n"));
      puts("");
    }

    {
       puts("functors");
      A ds([=]{ printf("deaf/silent %d\n", argc); });         printf("-> %s\n", ds("ds"));
      A s([=](const char *a){ printf("silent(%s) %d\n", a, argc); });           printf("-> %s\n", s("s"));
      A d([=]{ printf("deaf %d\n", argc); return "deaf"; });  printf("-> %s\n", d("d"));
      A n([=](const char *a){ printf("normal(%s) %d\n", a, argc); return "normal";}); printf("-> %s\n", n("n"));
    }

    return 0;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...