How to use std::expected in conjunction with std::transform?

545 Views Asked by At

I have a flatbuffer. This flatbuffer has a vector<T> that I want to transform using std::transform into a std::vector<U>.

It is possible that T can contain values that are not valid - because they are a plain integer converted to an enum. In this case, I want to abort the whole transform process.

Currently I throw an exception within the lambda of std::transform and catch it in the function that calls the transform function.

std::transform(deserialized_level.rooms.cbegin(), deserialized_level.rooms.cend(), constant_level_data.rooms.begin(),
    [&out_progress, &deserialized_rooms, &deserialized_level](const std::unique_ptr<room_t>& deserialized_room) {
        auto r = room_info{};
        r.position = int3(deserialized_room->position->x, deserialized_room->position->y, deserialized_room->position->z);
        r.sector_width = (unsigned char)(deserialized_room->sector_count_x);
        r.sector_length = (unsigned char)(deserialized_room->sector_count_z);
        auto sector_dimension = sector_size::normal;
        switch(deserialized_room->square_size) {
            case 256:
                sector_dimension = sector_size::smallest;
                break;
            case 512:
                sector_dimension = sector_size::smaller;
                break;
            case 1024:
                sector_dimension = sector_size::normal;
                break;
            case 2048:
                sector_dimension = sector_size::large;
                break;
            case 4096:
                sector_dimension = sector_size::very_large;
                break;
            case 8192:
                sector_dimension = sector_size::huge;
                break;
            case 16384:
                sector_dimension = sector_size::gigantic;
                break;
            default:
                throw std::runtime_error("Unknown sector size detected!" + std::to_string(deserialized_room->square_size));
        }
        //...//
        return r;
});

How would I model this with std::expected? A std::vector<std::expected<T,E>> seems kinda silly.

1

There are 1 best solutions below

4
Benjamin Buch On BEST ANSWER

If the transformation is to be aborted at the first unexpected value, an exception in the lambda is the right choice. It is the only way to cancel a std::transform.

What you want as a result is probably std::expected<std::vector<U>, std::string>. To do this, you put the std::transform call into a function that returns a corresponding std::expected.

#include <algorithm>
#include <expected>
#include <string>
#include <vector>

struct room { int size; };
struct room_info { std::string category;};

std::expected<std::vector<room_info>, std::string>
info_of(std::vector<room> const& rooms) try {
    std::vector<room_info> infos;
    infos.reserve(rooms.size());
    std::ranges::transform(rooms, std::back_inserter(infos),
        [](room const& r) -> room_info{
            using namespace std::literals;
            switch(r.size) {
                case 256:
                    return {"smallest"s};
                case 1024:
                    return {"normal"s};
                case 4096:
                    return {"very_large"s};
                case 16384:
                    return {"gigantic"s};
                default:
                    throw "invalid room size"s;
            }
        });
    return infos;
} catch (std::string error) {
    return std::unexpected(std::move(error));
}

Note that you can still throw other exceptions normally. The std::expected here only receives throw's with std::string.

You can use it like this:

#include <iostream>

void print(std::expected<std::vector<room_info>, std::string> const& info) {
    if(info) {
        auto const& list = info.value();
        std::cout << "[";
        if(!list.empty()) {
            std::cout << list[0].category;
            for(std::size_t i = 1; i < list.size(); ++i) {
                std::cout << ", " << list[i].category;
            }
        }
        std::cout << "]\n";
    } else {
        std::cout << info.error() << '\n';
    }
}

int main() {
    print(info_of({{256}, {16384}, {1024}}));
    print(info_of({{256}, {16385}, {1024}}));
}
[smallest, gigantic, normal]
invalid room size