C++ funkcionálny typ

C++ funkcionálny typ
« kdy: 10. 09. 2020, 01:32:12 »
Dobrý deň včera som narazil na jeden problém s funkcionálnymi typmi v C++. Išlo tam o jednu špecifickú higher-order funkciu (tú sem ťahať nebudem), ale skúsim to vysvetliť na príklade - bežnej higher-order funkcii map, ktorú asi pozná každý programátor.

dajme tomu že máme "modul" Vector a v ňom funkciu Vector::map ktorá má dva parametre:

1. mapper: Mapper<T1, T2>
2. list: vector<T>

Typ Mapper je funnckionálny typ:
Kód: [Vybrat]
template<typename T1, typename T2> using Mapper = T2(__stdcall*)(T1 item);
Funkcia Vector::map vyzerá nejako takto:
Kód: [Vybrat]
static struct Vector
{
template<typename T1, typename T2>
static std::vector<T2> map(const Mapper<T1, T2> mapper, const std::vector<T1> list)
{
...implementácia
}
}

Dajme tomu že chcem druhú mocninu položiek vektora:

Kód: [Vybrat]
const auto numbers = std::vector<int>{ 1, 2, 3 };
const auto squares = Vector::map(
(Mapper<int, int>)[](int a) -> int { return a * a; },
numbers
);
Vector::dump(squares);

Zatiaľ je všetko ok, no problém nastane keď chcem do tej lambdy dostať nejaké dáta z vonku. Dajme tomu, že mám číslo a všetky položky vectoru chcem vynásobiť týmto číslom. Ako to číslo dostanem do vnútra funkcie? Ak povolím lambde captures "[=]" alebo "[&]" tak už tá lambda nesedí s typom Mapper a (takú) lambdu v tomto prípade nemôžem použiť:

Kód: [Vybrat]
const auto numbers = std::vector<int>{ 1, 2, 3 };
const auto multiplier = 5; //ako tam dostanem tuto premennu?
const auto squares = Vector::map(
(Mapper<int, int>)[=](int a) -> int { return a * multiplier; },  // pri použití [=] alebo [&] prekladač vyhodí chybu
numbers
);
Vector::dump(squares);

Rovnako nejde do parametra vložiť metódu objektu (dá sa vložiť len statická metóda bez this). Vo funkcionálnych jazykoch by som použil buď currying, lambdu, ale C++ je na toto príliš striktné. Jediné čo ma napadlo je použiť statickú triedu so statickým memberom, lenže to je prasárna (a dosť nebezpečná prasárna). Máte nejaký nápad ako tento problém obísť? Vopred ďakujem.


Re:C++ funkcionálny typ
« Odpověď #1 kdy: 10. 09. 2020, 04:35:14 »
Kód: [Vybrat]
template<typename T1, typename T2> using Mapper = std::function<T2(T1)>;
To je všechno. Kdyby tě zajímalo, tak to uvnitř funguje, tak klíčová fráze je type erasure.

Re:C++ funkcionálny typ
« Odpověď #2 kdy: 10. 09. 2020, 07:55:01 »
https://www.modernescpp.com/index.php/c-core-guidelines-type-erasure

type erasure pomoci: void*, objekty se spolecnym predkem, templaty.

Re:C++ funkcionálny typ
« Odpověď #3 kdy: 10. 09. 2020, 12:21:16 »
jakej má smysl tohle:

Kód: [Vybrat]
static struct Vector
{
template<typename T1, typename T2>
static std::vector<T2> map(const Mapper<T1, T2> mapper, const std::vector<T1> list)
{
...implementácia
}
}

template uvnitř struktury ???

Re:C++ funkcionálny typ
« Odpověď #4 kdy: 10. 09. 2020, 15:16:42 »
A co třeba takto:
Kód: [Vybrat]
#include <vector>
#include <type_traits>
#include <cassert>

template<typename T, typename R = T>
constexpr auto mul_fun(T multiplier) {
    return [multiplier](auto a) -> R {
        return a * multiplier;
    };
};

template<typename T, typename F>
constexpr auto map(F&& f, const std::vector<T>& v) {
    std::vector<std::invoke_result_t<std::remove_cvref_t<F>, T>> ret;
    ret.reserve(v.size());
    std::transform(std::begin(v), std::end(v), std::back_inserter(ret), std::forward<F>(f));
    return ret;
}

int main() {
    constexpr auto f = mul_fun(5);
    static_assert(f(3) == 15);
    std::vector v1{1, 2, 3};
    auto v2 = map(f, v1);
    assert((v2 == std::vector{5, 10, 15}));
    auto v3 = map(mul_fun<double>(2), v2);
    assert((v3 == std::vector{10., 20., 30.}));
    return 0;
}


Re:C++ funkcionálny typ
« Odpověď #5 kdy: 11. 09. 2020, 17:52:53 »
linuxák a alexbbx:

Ďakujem za tipy na to type erasure. Prečítam si o tom.

...

Ahoj veľmi pekne ďakujem za príklad. Večer vyskúšam.

template uvnitř struktury ???

- Statickú štruktúru používam ako náhradu za modul, nakoľko C++ bude obsahovať plnohodnotné moduly až od verzie 20. Mohol by som použiť aj NS, ale ten neobsahuje modifikátory prístupu (a my potrebujeme mať možnosť niektoré časti modulu skryť pred vonkajším svetom).

- Struct je v C++ úplne to isté čo class s tým rozdielom že membery sú implicitne public (a dedenie je tiež implicitne
 public)