Не рекурсивно посещать пользовательский вариант - как элегантно вернуть значение? - PullRequest
1 голос
/ 04 марта 2020

Я пишу базовую версию std::variant для личного проекта и опыта обучения. Стратегия посещения, которую я хочу реализовать, представляет собой цепочку if...else if, а не таблицу указателей функций constexpr. Причина в том, что последний, как известно, трудно компилятору оптимизировать, и легко создать эталон, где std::visit побежден цепочкой if...else if.

Я пытаюсь реализовать это с помощью сложить выражения , но я не смог найти способ вернуть значение, когда найден правильный посетитель. Вот что у меня есть:

template <typename... Ts> 
struct my_variant 
{
    std::byte _buffer[std::max({sizeof(Ts)...})];
    std::size_t _discriminator;

    // ... 

    auto match(auto&&... fs) 
    {
        overload_set matcher(std::forward<Fs>(fs)...);

        [&]<std::size_t... Is>(std::index_sequence<Is...>) 
        {
            ([&]
            {
                if (_discriminator == Is) 
                {
                    // How to return from here?
                    matcher(*reinterpret_cast<Ts *>(&_buffer));
                }
            }(), ...);
        }
        (std::make_index_sequence_for<Ts...>{});
    }
};

Моя текущая стратегия - создать std::index_sequence для всех типов в варианте, затем сложить оператор запятой, чтобы компилятор генерировал кучу if заявления. Поскольку if не является выражением, мне пришлось заключить его в лямбда-выражение , чтобы можно было его сложить. Если я попытаюсь вернуться, я вернусь из самой лямбды, и это не распространяется на верхние уровни.

Я мог бы использовать буфер для хранения результата, а затем вернуть его, но это противоречит цели как это предотвратит RVO.

Есть ли способ, которым я могу писать match не рекурсивно и по-прежнему возвращать результат посетителя, позволяющий RVO иметь место?

1 Ответ

1 голос
/ 04 марта 2020

Вам нужно выбрать оператора, который не сбрасывает значение.

template <typename T>
struct result { // aka std::optional
    std::aligned_storage_t<T> store;
    bool has_value;

    result() : has_value(false) {}
    result(T t) : new(store) T(std::move(t)), has_value(true) {}

    const result & operator| (const result & other) const { return has_value ? *this : other; }

    T get() { return std::move(*reinterpret_cast<T *>(store)); }
};

template <typename... Ts> 
struct my_variant 
{
    std::byte _buffer[std::max({sizeof(Ts)...})];
    std::size_t _discriminator;

    // ... 

    auto match(auto&&... fs) 
    {
        overload_set matcher(std::forward<Fs>(fs)...);

        using result_t = result<std::common_type_t<std::invoke_result_t<matcher, Ts>...>>;

        return [&]<std::size_t... Is>(std::index_sequence<Is...>) 
        {
            return ([&]() -> result_t
            {
                if (_discriminator == Is) 
                {
                    // How to return from here?
                    return matcher(*reinterpret_cast<Ts *>(&_buffer));
                }
                return result_t{};
            }() | ...);
        }
        (std::make_index_sequence_for<Ts...>{}).get();
    }
};
...