如何将 std::expected 与 std::transform 结合使用?

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

提问人:Raildex 提问时间:3/12/2023 更新时间:3/12/2023 访问量:314

问:

我有一个平坦的缓冲区。这个 flatbuffer 有一个我想用它转换为 .vector<T>std::transformstd::vector<U>

可能包含无效的值 - 因为它们是转换为枚举的纯整数。在这种情况下,我想中止整个转换过程。T

目前,我在 lambda 中抛出一个异常,并在调用 transform 函数的函数中捕获它。std::transform

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;
});

我将如何对此进行建模?A似乎有点傻。std::expectedstd::vector<std::expected<T,E>>

C++ 异常 std-expected

评论

0赞 Paul Sanders 3/12/2023
你所拥有的有什么问题?甚至可以并行化它,而你正在做它。
0赞 Benjamin Buch 3/12/2023
您希望在第一个错误后继续转换还是应该中断?
0赞 Raildex 3/12/2023
In this case, I want to abort the whole transform process.

答:

1赞 Benjamin Buch 3/12/2023 #1

如果要在第一个意外值处中止转换,则 lambda 中的异常是正确的选择。这是取消 .std::transform

结果你想要的可能是.为此,将调用放入返回相应 .std::expected<std::vector<U>, std::string>std::transformstd::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));
}

请注意,您仍然可以正常抛出其他异常。这里只接收带有 的 。std::expectedthrowstd::string

你可以像这样使用它:

#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

评论

0赞 Paul Sanders 3/13/2023
这些本身都没有错,但是让异常传播到您想要处理的程度不是很有意义吗?
0赞 Benjamin Buch 3/13/2023
@PaulSanders 不,你不能这么说。这取决于您希望如何应对相应的问题。这个例子很好地表明,异常和 std::expected 是甚至可以一起使用的正交技术。解析错误经常发生,因此在正常的控制流中处理。但是,lambda 也可能抛出bad_alloc。这种情况很少发生,因此完全通过异常处理。
0赞 Paul Sanders 3/13/2023
这一论点缺乏实质内容。,特别是,趋向于终末期。std::bad_alloc
0赞 Benjamin Buch 3/13/2023
@PaulSanders 是的,当然是一个非常糟糕的例子。只需假设任何其他例外。之所以命名,只是因为它是上述代码中唯一可能发生的其他事情。但这仅仅是由于最小示例的简单性。更复杂的转换 lambda 代码可能会引发其他异常 ;-)bad_allocbad_alloc