Home > database >  Is it possible to create a `map_error` function that takes a lambda?
Is it possible to create a `map_error` function that takes a lambda?

Time:04-02

I am trying to create a map_error method attached to a std::expected type, or something similar. I can't seem to figure out the template meta programming. Is this possible to do something similar to this:

expect<int, fmt_err, io_err> square(int num) {
    if (num % 1)
        return fmt_err{};
    else if (num < 5)
        return io_err{};
    else
        return num * num;
}

square(i)
    .map_error([](fmt_err err) {
        return 10;
    })
    .map_error([](const io_error& err) {
            return 10;
    });

This is what I have so far, I can't seem to get it compiling though. For every .map_error it should remove one type, and then return a sub type of errs with the single error removed.

#include <type_traits>
#include <memory>

template<class...>struct types{using type=types;};

template<class Sig> struct args;
template<class R, class...Args>
struct args<R(Args...)>:types<Args...>{};
template<class Sig> using args_t=typename args<Sig>::type;


struct err
{
    virtual ~err() {}
};

struct fmt_err : public err
{
};

struct io_err : public err
{
};

template<typename...> struct errs;

template<typename R, typename T, typename... Ts>
struct errs<R, T, Ts...> : public errs<R, Ts...>
{
    using self = errs<T, Ts...>;
    using args = errs<R, Ts...>;

    using args::args;

    errs(R&& result) : errs(std::move(result))
    {
        
    }

    errs(T t)
    {

    }


};

template<typename R, typename T>
struct errs<R, T>
{
    errs() = default;

    errs(const R& result) : result(result)
    {

    }

    errs(R&& result) : result(std::move(result))
    {

    }

    errs(std::unique_ptr<err> error, int index)
        : error(std::move(error))
        , index(index)
    {

    }

    R result;
    int index = -1;
    std::unique_ptr<err> error;
};

template<typename...> struct expect;


template <typename T, typename E, typename... Es>
struct expect<T, E, Es...> : public expect<T, errs<T, E, Es...>>
{
    using base = expect<T, errs<T, E, Es...>>;
    using base::base;


    expect(T&&) {

    }

    expect(E&&) {

    }

    // template<int N, typename... Ts> using NthTypeOf = typename std::tuple_element<N, std::tuple<Ts...>>::type;

    // template<typename En = std::enable_if_t<true, NthTypeOf<0, Es...>>>
    // expect(En&&) : expect(std::make_unique<En>(), sizeof...(Es)) {
    // }
};

template <typename T, typename E, typename... Es>
struct expect<T, errs<T, E, Es...>>
{
    errs<T, E, Es...> errors;

    expect() {
    }

    expect(T&&)
    {

    }

    expect(std::unique_ptr<err>&& error, int index)
    {
        errors.error = std::move(error);
    }

    template<typename F>
    expect<T, Es...> map_error(F&& func) {
        
        if (errors.index == 0) {
            return errors.result;
        } else if (errors.index == sizeof...(Es)) {
            return func(*static_cast<E*>(errors.error.get()));
        } else if (errors.index < sizeof...(Es)) {
            return errors;
        }
    }
};

expect<int, fmt_err, io_err> square(int num) {
    if (num % 1)
        return fmt_err{};
    else if (num < 5)
        return io_err{};
    else
        return num * num;
}

int main()
{
    for(int i = 0; i < 10;   i)
    {
        square(i)
            .map_error([](fmt_err err){
                return 10;
            });
    }
}

CodePudding user response:

You are missing three constructors, here is a working version of your code: https://godbolt.org/z/cxY8Yzzq1

#include <memory>
#include <type_traits>

template <class...>
struct types {
    using type = types;
};

template <class Sig>
struct args;
template <class R, class... Args>
struct args<R(Args...)> : types<Args...> {};
template <class Sig>
using args_t = typename args<Sig>::type;

struct err {
    virtual ~err() {}
};

struct fmt_err : public err {};

struct io_err : public err {};

template <typename...>
struct errs;

template <typename R, typename T, typename... Ts>
struct errs<R, T, Ts...> : public errs<R, Ts...> {
    using self = errs<T, Ts...>;
    using args = errs<R, Ts...>;

    using args::args;

    errs(R&& result) : errs(std::move(result)) {}

    errs(T t) {}
};

template <typename R, typename T>
struct errs<R, T> {
    errs() = default;

    // ---------------------------------
    // needed in order to be able to make copies of errs, copying was disabled by storing a unique_ptr inside the class
    errs(const errs& rref) :
    result(rref.result),
    index(rref.index),
    error(new err(*rref.error))
    {
    }
    // ---------------------------------

    errs(const R& result) : result(result) {}

    errs(R&& result) : result(std::move(result)) {}

    errs(std::unique_ptr<err> error, int index)
        : error(std::move(error)), index(index) {}

    R result;
    int index = -1;
    std::unique_ptr<err> error;
};

template <typename...>
struct expect;

template <typename T, typename E, typename... Es>
struct expect<T, E, Es...> : public expect<T, errs<T, E, Es...>> {
    using base = expect<T, errs<T, E, Es...>>;
    using base::base;

    expect(T&&) {}

    expect(E&&) {}

    // ---------------------------------
    // needed in order to convert from io_err' to 'expect<int, fmt_err, io_err>' in line 123
    expect(Es&&...) {}
    // ---------------------------------

    // template<int N, typename... Ts> using NthTypeOf = typename
    // std::tuple_element<N, std::tuple<Ts...>>::type;

    // template<typename En = std::enable_if_t<true, NthTypeOf<0, Es...>>>
    // expect(En&&) : expect(std::make_unique<En>(), sizeof...(Es)) {
    // }
};

template <typename T, typename E, typename... Es>
struct expect<T, errs<T, E, Es...>> {
    errs<T, E, Es...> errors;

    expect() {}

    expect(T&&) {}

    // ---------------------------------
    // needed because you pass an int, not an rvalue in line 110 (return expect<T, Es...>(errors.result);)`
    expect(const errs<T, E, Es...>& errors) : errors(errors) {}
    // ---------------------------------

    expect(std::unique_ptr<err>&& error, int index) {
        errors.error = std::move(error);
    }

    template <typename F>
    expect<T, Es...> map_error(F&& func) {
        if (errors.index == 0) {
            return expect<T, Es...>(errors.result);
        } else if (errors.index == sizeof...(Es)) {
            return func(*static_cast<E*>(errors.error.get()));
        } else if (errors.index < sizeof...(Es)) {
            return errors;
        }
    }
};

expect<int, fmt_err, io_err> square(int num) {
    if (num % 1)
        return fmt_err{};
    else if (num < 5)
        return io_err{};
    else
        return num * num;
}

int main() {
    for (int i = 0; i < 10;   i) {
        square(i).map_error([](fmt_err err) { return 10; });
    }
}

Missing constructors:

  • errs<R, T>::errs(const errs& rref); // needed in order to be able to make copies of errs, copying was disabled by storing a unique_ptr inside the class
  • expect<T, E, Es...>::expect(Es&&...) // needed in order to convert from io_err' to 'expect<int, fmt_err, io_err>' in line 123
  • expect<T, errs<T, E, Es...>>::expect(const errs<T, E, Es...>& errors) // needed because you pass an int, not an rvalue in line 110
  • Related