6

Opracowuję stosunkowo prosty program (właściwie kalkulator). Jednak zdecydowałem się uczynić wszystkie elementy mojego programu tak ogólne, jak to możliwe, ponieważ:Funkcje polimorficzne (ogólne) jako argumenty w C++

  1. To dobra praktyka.
  2. Zachowuje rzeczy interesujące.

W ramach tego programu używam klasy Tuple, którą piszę. Wiem, że klasa już istnieje, ale lubię mieć pełną kontrolę nad moim kodem i to jest tylko ćwiczenie.

Jedna rzecz, którą muszę zrobić, to przekształcić krotkę wyrażeń (gdzie same wyrażenia są ogólne) w krotkę zawierającą wyniki ocen wyrażeń. Krótko mówiąc, mam (z części trywialne pominięta):

template <class T> 
class Expression { 

    public: 
     virtual T Eval() = 0; 

    // ... 
}; 

template <class First, class ... Rest> 
class Tuple { 

    // ... 

    private: 
     First first; 
     Tuple<Rest ...> rest; 
}; 

I chciałbym się specjalizować na krotki generycznego typu jak ten:

template <template <class> class R, class First, class ... Rest> 
class Tuple<R<First>, R<Rest> ...> { 

    // and here is the problem: 
    Tuple<First, Rest ...> Transform(function<template<class T> T(R<T>)>); 
}; 

Po co może zrobić to:

template <class T> // There has to be a better way to do this 
T Eval(Expression<T>& expr){ 
    return expr.Eval(); 
} 

// ... 
Tuple<First, Rest ...> tuple = exprs.Transform(Eval); 

Istnieje kilka miejsc, gdzie tu nie jestem pewien, jak się do tego zabrać rzeczy i prawdziwego eksperta, który mógłby mi pomóc tutaj będą mile widziane. Spodziewam się, że ten kod nie skompiluje się z powodu drobnych wad, ale nie o to chodzi - Moim głównym zmartwieniem jest linia, którą zaznaczyłem. Jeśli dobrze pamiętam z tego krótkiego okresu, nauczyłem się Haskella, funkcja ta powinna być w Rank-2 (jeśli nie, proszę o komentarz, a ja usunę tag). To po prostu nie wygląda dobrze. Czy jest jakiś sposób to zrobić?

Aktualizacja:

poradzono mi, aby spróbować przekazać funktor z ogólnej operator() jako szablon argumentu, ale to nie działa.

+0

Spróbuj 'boost :: mpl :: transform'. –

+0

@ n.m. - To nie zapewnia funkcjonalności, której szukam. – user2008934

+0

@dyp - Tak, robię, To jest czysta wirtualna funkcja. Ale nie o to chodzi. Chodzi o to - jak przekazać funkcję ogólną jako argument? – user2008934

Odpowiedz

2

Myślę, że można to zrobić po prostu bez C++ 14 w ogóle. Mam zamiar założyć kilka rzeczy o tym, jak zbudowany jest Twój Tuple, a mianowicie, że te dwa ctors istnieje:

Tuple(First, Rest...);    // (1) 
Tuple(First, const Tuple<Rest...>&); // (2) 

Musimy jeden typ cechę: dana funkcja, że ​​jesteśmy z przekształcenia, musimy wiedzieć jakie rodzaje produkuje:

template <typename T, typename F> 
using apply_t = decltype(std::declval<F>()(std::declval<T>())); 

(Boga kocham C++ 11)

Dzięki temu możemy określić zwrot wpisać łatwo, a to tylko kwestia wywołaniu funkcji rekursywnie:

template <typename First, typename... Rest> 
struct Tuple 
{ 
    template <typename F> 
    Tuple<apply_t<First, F>, apply_t<Rest, F>...> 
    Transform(F func) 
    { 
     return {func(first), rest.Transform(func)}; // hence the need 
                // for ctor (2) 
    }; 
}; 

(W zależności od sposobu napisałeś Tuple może lub nie może wymagać przypadek bazowy dla trywialne przekształcić że po prostu zwraca Tuple<> czy przypadek bazowy, który właśnie wraca Tuple<apply_t<First, F>>. Tak czy inaczej, nie jest wielka sprawa).

Nie musisz nawet w ogóle specjalizować Tuple. Musisz tylko przekazać właściwe funktory. Na przykład:

struct Zero 
{ 
    template <typename T> 
    int operator()(T) { return 0; } 
}; 

struct Incr 
{ 
    template <typename T> 
    T operator()(T x) { return x + 1; } 
}; 

Tuple<int, double, char> tup(1, 2.0, 'c'); 
auto z = tup.Transform(Zero{}); // z is Tuple<int, int, int>{0, 0, 0} 
auto i = tup.Transform(Incr{}); // i is Tuple<int, double, char>{2, 3.0, 'd'} 

Here „s pełny przykład kodu, rejestrowanie wszystkich rodzajów zbyt. Oczywiście w C++ 14 możemy wykonywać następujące:

auto i2 = tup.Transfom([](auto x) -> decltype(x) {return x+1; }); 
// i2 is a Tuple<int, double, char>{2, 3.0, 'd'}; 
// without the trailing decltype, it gets deduced as Tuple<int, double, int>. 
3

Zwykła sztuczka w C++ 14 jest użycie niektórych index_sequence (patrz here) i wtedy coś takiego:

template<typename ... Args, size_t ... I> 
auto evaluate(Tuple<Args ...> const& t, index_sequence<I...>) 
{ 
    return make_tuple(evaluate(get<I>(t))...); 
} 

patrz np this answer na przykład tego podejścia (jedyną różnicą jest to, że tutaj dodatkowo wywoływane jest wywołanie funkcji).

Zatem, co trzeba tutaj w swojej klasie Tuple na to:

  • Implementacja niestandardowego get funkcji, która zachowuje się podobnie do std::get, tj akceptuje zmiennej liczbie argumentów argumentów indeksowych.
  • Implementacja niestandardowej funkcji make_tuple, która zachowuje się podobnie do std::make_tuple i tworzy krotkę z listy rozdzielanej przecinkami.

Ponadto potrzebny jest szablon funkcji evaluate, który jest w stanie ocenić pojedyncze wyrażenie, ale domyślam się, że już to masz.


EDIT: zdałem sobie sprawę, że powyższe nie może być bardzo pomocne dla Ciebie. Raczej należy zauważyć, że można to zrobić również rekurencyjnie:

template<typename ... Args> 
auto evaluate(Tuple<Args ...> const& t) 
{ 
    return tuple_cat(make_tuple(evaluate(t.first)), evaluate(t.rest)); 
} 

template<typename T> auto evaluate(Tuple<T> const& t) { return evaluate(t.first); } 

Ponownie, wymagają make_tuple funkcję, a concatenator krotny tuple_cat i oceniającego pojedynczej ekspresji evaluate.

+0

Ekwiwalent dla std :: get i std :: make_tuple są łatwe i już zaimplementowałem je w nieco innym trybie. Nadal nie wiem, jak napisać funkcję Transform, która jest ważną częścią. – user2008934

+0

@ user2008934: [tutaj] (http://coliru.stacked-crooked.com/a/1383d7fbf9846ccb) jest podstawową implementacją, która powinna przybliżyć ci się do tego, co chcesz zrobić (dla dokładnego dopasowania potrzebujesz 'tuple_cat ', którego byłem zbyt leniwym do wdrożenia). Mam nadzieję, że to pomoże. – davidhigh

+0

Jak to jest ogólne? Nie widzę, jak to przedłużyć w podobnym przypadku (na przykład krotka lista różnych typów -> krotka sumy każdej listy) - Wydawałoby się, że trzeba powtarzać ten kod dla każdego takiego przypadku. – user2008934