【问题标题】:Non-recursively visit a custom variant - how to elegantly return a value?非递归访问自定义变体 - 如何优雅地返回值?
【发布时间】:2020-03-04 10:37:18
【问题描述】:

我正在为个人项目和学习体验编写std::variant 的准系统版本。我要实现的访问策略是if...else if 链,而不是constexpr 函数指针表。原因是后者对于编译器来说是出了名的难以优化,并且很容易产生一个基准,其中std::visitif...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 不是一个表达式,我不得不将它包装成一个lambda 表达式 以便能够折叠它。如果我尝试返回,我将从 lambda 本身返回,并且不会传播到上层。

我可以使用缓冲区来存储结果,然后将其返回,但这违背了目的,因为它会阻止 RVO。

有没有一种方法可以让我以非递归方式编写 match 并且仍然返回访问者的结果以允许 RVO 发生?

【问题讨论】:

标签: c++ templates variant c++20 fold-expression


【解决方案1】:

您需要选择一个不会丢弃该值的运算符。

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

【讨论】:

  • 这很聪明,但并没有真正解决原来的问题。您仍然需要一个缓冲区来存储结果,并且还有额外的检查和额外的移动构造函数调用。
猜你喜欢
  • 1970-01-01
  • 2018-10-31
  • 1970-01-01
  • 2019-10-15
  • 2014-10-03
  • 2011-02-11
  • 1970-01-01
  • 2014-12-02
  • 2010-10-19
相关资源
最近更新 更多