Функтор C++ (отображение)

Я создал класс either<l, r> очень похожий на Either a b в Haskell. Я также реализовал функцию map прямо в классе; вот как выглядит код:

template<typename l, typename r>
class either 
{
  template<typename b>
  either<l, b> map(const std::function<b (r)> &f)
  {
    // ...
  }
};

Теперь я хочу изолировать функцию map, чтобы создать абстрактный базовый класс с именем functor.

template<typename a, template <a> class derived>
class functor
{
public:
  virtual ~functor();

  template<typename b>
  derived<b> map(const std::function<b (a)> &f) = nullptr;
};

either унаследует этот класс:

class either : functor<r, either<l, r>>

однако это недопустимый C++, поскольку функции-члены шаблона не могут быть виртуальными.

Более того, я попытался проверить, может ли <r, either<l, r>> соответствовать <a, derived<a>> в functor<r, either<l, r>> (или любому другому шаблону в этом отношении), но не смог, поскольку у него есть два параметра шаблона. Также обратите внимание, что другие производные классы функтора могут иметь другое количество аргументов шаблона, которые не имеют отношения к функтору.

Можно ли выразить базовый класс функтора в шаблонах C++?


person Maarten    schedule 21.07.2014    source источник
comment
Интересно, почему вы хотите сделать это virtual. Это ничего не даст в вашем случае использования наследования, если вы не добавите возможность внедрить класс для наследования через параметр шаблона, и в этот момент вам не понадобится виртуальный.   -  person pmr    schedule 22.07.2014
comment
Разве вы не хотите что-то вроде template<typename a, typename b, typename C> typename C::rebind<b>::other map(const std::function<b (a)> &f, const C& c);   -  person Jarod42    schedule 22.07.2014
comment
Представьте на мгновение, что вам удалось сделать functor::map виртуальным. Как именно вы планируете его использовать таким образом, который требует полиморфизма? Как, по-вашему, будет выглядеть место для звонков?   -  person Igor Tandetnik    schedule 22.07.2014
comment
@Igor Представьте себе некоторую общую структуру данных D, содержащую числа с плавающей запятой (D<float> instance) и производную от functor<float, D<float>>. Теперь возьмем функцию std::function<int (float)> discretize. Я хочу, чтобы моя структура данных могла превратиться из D‹float› в D‹int›, написав instance.map(discretize);. Здесь компилятор должен сделать вывод, что b в объявлении map является int, и он должен искать определения функции D<float>.map<int>(const std::function<int (float)> &) в производном классе.   -  person Maarten    schedule 22.07.2014
comment
Никакая часть вашего сценария не требует, чтобы map был виртуальным. Вы не вызываете map через указатель или ссылку на functor. Я вообще не понимаю, зачем вам functor.   -  person Igor Tandetnik    schedule 22.07.2014
comment
Все в порядке! Что, если у меня есть функция template<typename <float> DS> DS<int> discretizeAny(DS<float> &ds) { return ds.map(discretize); }, которая дискретизирует любую структуру данных, являющуюся функтором, будь то D, list, tree и так далее? Я не спрашиваю, зачем мне нужен функтор, я спрашиваю, возможно ли определить класс на C++, который гарантирует, что все неабстрактные производные классы могут быть отображены.   -  person Maarten    schedule 22.07.2014
comment
Я не уверен, что понимаю. Ваша гипотетическая функция принимает в качестве параметра DS, а не functor. Каково предполагаемое определение DS?   -  person Igor Tandetnik    schedule 22.07.2014
comment
DS — это любая производная структура данных от функтора (она реализует карту)   -  person Maarten    schedule 22.07.2014
comment
Почему оно должно происходить от functor, когда discretizeAny не упоминает и не использует functor ни в какой форме? discretizeAny будет вполне успешно работать с любым классом, имеющим функцию-член с именем map, виртуальную или иную, с подходящей подписью. См. также утиный ввод   -  person Igor Tandetnik    schedule 22.07.2014
comment
это то, что вы ищете?   -  person Igor Tandetnik    schedule 22.07.2014
comment
Мне нужна более сильная гарантия, чем то, что класс DS просто имеет функцию с именем map с совместимой сигнатурой типа. Пожалуйста, обратите внимание, что я задаю вопрос из искреннего интереса к языку программирования C++, а не потому, что у меня крайний срок разработки программного обеспечения.   -  person Maarten    schedule 22.07.2014
comment
Я не понимаю, как производное от functor обеспечивает более надежную гарантию, чем функция-член с именем map. Какие дополнительные гарантии вы хотели бы иметь и почему?   -  person Igor Tandetnik    schedule 22.07.2014
comment
Функтор должен подчиняться двум законам map id = id и map (p . q) = (map p) . (map q). Конечно, нереально проверить, соблюдаются ли эти законы для производного класса в таком языке программирования, как C++, поэтому давайте пока проигнорируем это. Из-за ошибки программиста возможно, что не-функтор (у которого просто есть карта функций) передается функции, которая опирается на законы функтора. Строго типизированная система предупредит во время компиляции, что передаваемый объект не является производным от функтора. Утиная печать не уловила бы такую ​​ошибку программирования.   -  person Maarten    schedule 22.07.2014
comment
Я не уверен, что означают id и .. В любом случае, производное от functor, по-видимому, ничего из этого не гарантирует. Я также не уверен, что вы подразумеваете под нефунктором (у которого просто есть функция map). Класс, у которого есть функция map с подходящей сигнатурой, звучит для меня как функтор.   -  person Igor Tandetnik    schedule 22.07.2014
comment
Если вы просто хотите, чтобы ваши классы были помечены особым образом, определите struct functor{};, выведите свои классы из functor и в discretizeAll напишите static_assert(is_base_of<functor, decltype(ds)>::value, "must derive from functor");   -  person Igor Tandetnik    schedule 22.07.2014
comment
@Maarten: Обычный способ предоставить эту гарантию - НЕ наследование, это способ Java. В C++ мы используем диспетчеризацию тегов: template<class T> struct is_map_functor: std::false_type {}; template<> struct is_map_functor<discretizeAny>: std::true_type {}; См. также: std::is_fundamental и std::is_integral и тысячи им подобных. cplusplus.com/reference/type_traits/is_fundamental   -  person Mooing Duck    schedule 22.07.2014


Ответы (1)


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

Типичная реализация будет следующей:

#include <iostream>
#include <functional>

using namespace std;

template<typename a, class derived>
class functor
{
public:
    // You have to define the destructor
    virtual ~functor() {}

    template<typename b>
    // The full type of the derived class is known
    derived map(const std::function<b(a)> &f)
    {
        // Here you take advantage of the CRTP
        return static_cast<derived*>(this)->map(f);
    }
};

template<typename l, typename r>
// You have to put public as inheritance access level
class either : public functor<r, either<l, r>>
{ // The way you want to inherit implies that the base has no 
  // template template parameter, just template parameters !!
public:
    template<typename b>
    either<l, b> map(const std::function<b(r)> &f)
    {
        cout << "In derived" << endl;
        return  either<l, b>();
    }
};

int main() 
{
    // pointer to base class points to a derived object
    functor<int, either<int, int>> *ff = new either<int, int>();
    // map function will call the method of the derived class
    ff->map<int>([](int k){ return 1; });

    return 0;
}

Я взял на себя смелость указать некоторые вещи в комментариях. ХТН

person Nikos Athanasiou    schedule 21.07.2014