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

huangapple go评论60阅读模式
英文:

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

问题

我有一个FlatBuffer。这个FlatBuffer有一个vector<T>,我想要使用std::transform将其转换为一个std::vector<U>

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

目前,我在std::transform的lambda函数中抛出异常,然后在调用转换函数的函数中捕获它。

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

我如何使用std::expected来建模这个问题?一个std::vector<std::expected<T,E>>似乎有点愚蠢。

英文:

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

得分: 1

如果要在第一个意外值出现时中止转换,lambda 中的异常是正确的选择。这是取消 std::transform 的唯一方法。

您可能想要的结果是 std::expected<std::vector<U>, std::string>。要做到这一点,您可以将 std::transform 调用放入一个返回相应 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));
}

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

您可以像这样使用它:

#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

希望这有所帮助。

英文:

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&lt;std::vector&lt;U&gt;, std::string&gt;. To do this, you put the std::transform call into a function that returns a corresponding std::expected.

#include &lt;algorithm&gt;
#include &lt;expected&gt;
#include &lt;string&gt;
#include &lt;vector&gt;

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

std::expected&lt;std::vector&lt;room_info&gt;, std::string&gt;
info_of(std::vector&lt;room&gt; const&amp; rooms) try {
    std::vector&lt;room_info&gt; infos;
    infos.reserve(rooms.size());
    std::ranges::transform(rooms, std::back_inserter(infos),
        [](room const&amp; r) -&gt; room_info{
            using namespace std::literals;
            switch(r.size) {
                case 256:
                    return {&quot;smallest&quot;s};
                case 1024:
                    return {&quot;normal&quot;s};
                case 4096:
                    return {&quot;very_large&quot;s};
                case 16384:
                    return {&quot;gigantic&quot;s};
                default:
                    throw &quot;invalid room size&quot;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 &lt;iostream&gt;

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

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

huangapple
  • 本文由 发表于 2023年3月12日 17:03:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/75712048.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定