Шаблон класса, производный от другого типа шаблона класса - PullRequest
3 голосов
/ 23 мая 2019

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

Использование этого базового класса Functor:

template <typename TOut, typename TIn>
class Functor {
public:
    virtual
    ~Functor() {
    }

    virtual
    TOut operator()(TIn input) = 0;
};

Теперь я хочу написать класс, который будет инкапсулировать и запоминать функтор. В дополнение к инкапсуляции Functor, MemoizedFunctor сам по себе будет Functor. В результате получается 3 параметра шаблона.

Вот рабочий пример:

#include <unordered_map>

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn> {
public:
    MemoizedFunctor(F f) : f_(f) {
    }

    virtual
    ~MemoizedFunctor() {
    }

    virtual
    TOut operator()(TIn input) override {
        if (cache_.count(input)) {
            return cache_.at(input);
        } else {
            TOut output = f_(input);
            cache_.insert({input, output});
            return output;
        }
    }

private:
    F f_;
    std::unordered_map<TIn, TOut> cache_;
};

class YEqualsX : public Functor<double, double> {
public:
    virtual
    ~YEqualsX() {
    }

    double operator()(double x) override {
        return x;
    }
};

int main() {
    MemoizedFunctor<YEqualsX, double, double> f((YEqualsX())); // MVP

    f(0); // First call
    f(0); // Cached call

    return 0;
}

Я чувствую, что ДОЛЖЕН быть способ избавиться от необходимости указывать все 3 параметра шаблона. Учитывая, что функция передана конструктору MemoizedFunctor, я бы сказал, что все три параметра шаблона могут быть выведены.

Я не уверен, как переписать класс так, чтобы его использование не требовало всей спецификации шаблона.

Я попытался использовать умный указатель на Functor в качестве переменной-члена в MemoizedFunctor. Это исключает первый параметр шаблона, но теперь пользователь класса должен передать умный указатель на класс MemoizedFunctor.

Итак, я хотел бы, чтобы все аргументы шаблона MemoizedFunctor автоматически выводились при построении. Я считаю, что это возможно, потому что при построении все аргументы шаблона однозначны.

Ответы [ 2 ]

4 голосов
/ 24 мая 2019

In:

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn> { ... }

Если F должно быть реализацией Functor (что, я предполагаю, имеет место?), Вы можете добавить несколько псевдонимовна Functor, чтобы сделать вашу жизнь проще (это можно сделать внешне, а не внутренне, но кажется разумным сделать это навязчивым):

template <typename TOut, typename TIn>
class Functor {
public:
    using in_param = TIn;
    using out_param = TOut;
    // ... rest as before ...
};

А затем измените MemoizedFunctor, чтобы использовать эти псевдонимы напрямую.Вы на самом деле не имеете независимых параметров шаблона, они полностью зависимы, верно?

template <typename F>
class MemoizedFunctor : public Functor<typename F::out_param, typename F::in_param> { ... }

С этим изменением (и аналогичным образом измените ваше внутреннее использование TOut и TIn), это работает как нужно (так как, конечно, у нас теперь только один параметр шаблона, поэтому есть только один для предоставления):

MemoizedFunctor<YEqualsX> f(YEqualsX{});

И этот параметр может быть выведен через CTAD напрямую без каких-либо дальнейших изменений

MemoizedFunctor f(YEqualsX{});
4 голосов
/ 23 мая 2019

Итак, я хотел бы, чтобы все аргументы шаблона MemoizedFunctor автоматически выводились при построении. Я считаю, что это возможно, потому что в конструкции все аргументы шаблона однозначны.

Если я правильно понимаю, первый тип шаблона для MemoizedFunctor всегда является Functor<TOut, TIn> или чем-то, что наследуется от некоторого Functior<TOut, TIn>, где TOut и TIn являются вторым и третьим параметром шаблона для MemoizedFunctor.

Мне кажется, что вы ищете руководство по выводу.

Чтобы вывести второй и третий параметр шаблона, я предлагаю объявить (определение не требуется, поскольку используются только внутри decltype()) следующую пару функций

template <typename TOut, typename TIn>
constexpr TIn getIn (Functor<TOut, TIn> const &);

template <typename TOut, typename TIn>
constexpr TOut getOut (Functor<TOut, TIn> const &);

Теперь, используя decltype() и std::declval(), пользовательское руководство по выводу просто становится

template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      decltype(getIn(std::declval<F>()))>;

Ниже приведен полный пример компиляции

#include <unordered_map>

template <typename TOut, typename Tin>
class Functor
 {
   public:
    virtual ~Functor ()
     { }

    virtual TOut operator() (Tin input) = 0;
 };

template <typename TOut, typename TIn>
constexpr TIn getIn (Functor<TOut, TIn> const &);

template <typename TOut, typename TIn>
constexpr TOut getOut (Functor<TOut, TIn> const &);

template <typename F, typename TOut, typename TIn>
class MemoizedFunctor : public Functor<TOut, TIn>
 {
   public:
      MemoizedFunctor(F f) : f_{f}
       { }

      virtual ~MemoizedFunctor ()
       { }

      virtual TOut operator() (TIn input) override
       {
         if ( cache_.count(input) )
            return cache_.at(input);
         else
          {
            TOut output = f_(input);
            cache_.insert({input, output});
            return output;
          }
       }

   private:
      F f_;
      std::unordered_map<TIn, TOut> cache_;
 };

class YEqualsX : public Functor<double, double>
 {
   public:
      virtual ~YEqualsX ()
       { }

      double operator() (double x) override
       { return x; }
 };

template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      decltype(getIn(std::declval<F>()))>;

int main ()
 {
   MemoizedFunctor f{YEqualsX{}};

   f(0); // First call
   f(0); // Cached call
 }

- РЕДАКТИРОВАТЬ -

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

Например, функция не может вернуть массив в стиле C.

Это не проблема, определяющая TOut (тип, возвращаемый operator()) именно потому, что это тип, возвращаемый методом, и поэтому может быть возвращен также getOut().

Но это может быть (вообще говоря) проблемой для TIn: если TIn, например, int[4] (не может быть, в этом случае, потому что используется в качестве ключа для неупорядоченной карты но, повторяю, вообще говоря), int[4] не может быть возвращено getIn().

Вы можете обойти эту проблему (1), добавив структуру оболочки типа следующим образом

template <typename T>
struct typeWrapper
 { using type = T; };

(2) изменение getIn() для возврата оболочки TIn

template <typename TOut, typename TIn>
constexpr typeWrapper<TIn> getIn (Functor<TOut, TIn> const &);

и (3) изменение направляющей вычета для извлечения TIn из оболочки

template <typename F>
MemoizedFunctor(F)
   -> MemoizedFunctor<F,
                      decltype(getOut(std::declval<F>())),
                      typename decltype(getIn(std::declval<F>()))::type>;
...