STD :: посещение и STD :: вариант использования - PullRequest
1 голос
/ 22 января 2020
#include <variant>
#include <exception>
#include <type_traits>
#include <cassert>

template <typename T>
struct Promise {
    std::variant<
        std::monostate,
        std::conditional_t<std::is_void_v<T>, std::monostate, T>,
        std::exception_ptr
    > result_;

    T await_resume() const {
        assert(result_.index() > 0);
#if 1
        // old code that I want to optimise
        if (result_.index() == 2) {
            std::rethrow_exception(std::get<2>(result_));
        }
        if constexpr (!std::is_void_v<T>) {
            return std::get<1>(result_);
        }
#else
        // new code, won't compile
        return std::visit([](auto&& arg) {
            using TT = std::decay_t<decltype(arg)>;
            if constexpr (!std::is_same_v<TT, std::exception_ptr>) {
                std::rethrow_exception(arg);
            } else if constexpr (!std::is_void_v<T>) {
                return arg;
            }
        });
#endif
    }
};

template int Promise<int>::await_resume() const;
template std::exception_ptr Promise<std::exception_ptr>::await_resume() const;
template void Promise<void>::await_resume() const;

Promise :: await_resume - это простая функция, которая выполняет следующие действия:

  1. Если вариант содержит значение std::exception_ptr, перезапустите исключение.
  2. Если вариант содержит значение T (хотя T задается пользователями, также может быть std :: exception_ptr), вернуть его. Если тип T является пустым, ничего не делать.

Первоначально я реализую его, используя .index() check и std::get. Это работает, но std::get вещи генерируют дополнительные проверки внутри и std::__1::__throw_bad_variant_access() вещи, которые не ожидаются: https://godbolt.org/z/YnjxDy

Я хочу оптимизировать код, используя std :: visit согласно на cppreference , но не могу его скомпилировать.

Другая проблема заключается в том, что когда тип T равен std :: exception_ptr, как я могу узнать, должен ли я его выбросить?

Ответы [ 2 ]

1 голос
/ 22 января 2020

visit не «оптимизирует» код - это просто хороший шаблон для сравнения на variant, и особенно полезно убедиться, что вы не забыли ни о каких типах.

Но одно из требований visit заключается в том, что каждая альтернатива должна возвращать один и тот же тип. Это особенно проблематично c в вашем случае использования, поскольку должна быть возвращена только одна из ваших альтернатив ... так что она просто не подходит. Вам также нужно для обработки monostate дела в visit, и у вас действительно нет никакого способа сделать это (кроме ... броска?), Так что вам просто не повезло.

Версия, с которой вы работали раньше, прекрасно, я бы просто пометил ее типами, чтобы она была более выразительной:

struct Void { };

template <typename T>
struct Promise {
    using Value = std::conditional_t<std::is_void_v<T>, Void, T>;

    std::variant<
        std::monostate,
        Value,
        std::exception_ptr
    > result_;

    T await_resume() const {
        assert(not result_.valueless_by_exception());
        assert(not std::holds_alternative<std::monostate>(result_));

        if (auto* exc = std::get_if<std::exception_ptr>(&result)) {
            std::rethrow_exception(*exc);
        } else {
            if constexpr (not std::is_void_v<T>) {
                return std::get<T>(result_);
            }
        }
    }
}

Я думаю, что это немного лучше, чем использование 0, 1 и 2 явно.

Другая проблема состоит в том, что когда тип T равен std::exception_ptr, как я могу узнать, должен ли я его бросить?

Простой: Вы не бросаете его. Не используйте сильно различную семантику в обобщенном c коде, основанном на вашем типе. Promise<T>::await_resume() возвращает T, если оно содержит T. Promise<std::exception_ptr>::await_resume() возвращает exception_ptr. Хорошо.

Полагаю, на самом деле с моей реализацией, приведенной выше, использование явного get_if<exception_ptr> станет двусмысленным, что вызывает сожаление ... поэтому, возможно, 0 / 1 / 2 - это просто простой способ go.

0 голосов
/ 22 января 2020

Относится к ответу @ Барри, это моя окончательная версия:

T await_resume() const {
    if (auto* pep = std::get_if<2>(&result_)) {
        std::rethrow_exception(*pep);
    } else {
        if constexpr (!std::is_void_v<T>) {
            auto* pv = std::get_if<1>(&result_);
            assert(pv);
            return *pv;
        }
    }
}

Генерирует совершенную асм, без дополнительных проверок, без bad_variant_access sh* t: https://godbolt.org/z/96gF_J

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...