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.
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