c++ - c++17 std::variant 如何确定使用哪种类型?

标签 c++ c++17

我很好奇 std::variant 是如何工作的。

据我了解,它“基本上是”带有附加索引变量的 union 。 然而,我很困惑它如何确定将构造/分配有效地转发到哪种类型。我通过 godbolt.org 查看过它,它似乎确实非常有效,即使是从另一个变体复制时也是如此。

我已经考虑过如何实现类似变体的类型,但我想到的唯一方法是拥有某种辅助函数,将构造函数/分配/析构函数调用转发到基于一个索引参数。然而,这对于递归调用来说效率相当低,不是吗?

另一种方法是拥有一个具有固定类型的 union ,并且每当需要使用构造函数/析构函数等时。一个大的开关将根据索引变量决定使用哪个 union 成员,但这需要大量的代码膨胀并且仅适用于固定类型。

从我在 godbolt.org 上看到的情况来看,variant 的 gcc 实现调用了 std::__detail::__variant::__gen_vtable_impl,但我还没有找到任何关于它的作用的信息.

此外,例如,如果它们都可以从传递的参数类型“转换”,那么它如何确定在构造期间使用哪种变体类型? cppreference 表示类型必须是可转换的,但是如果有多种可能的可转换变体类型怎么办?

我尝试查看 gcc 的 stdlib 实现源代码,但它非常令人困惑,所以如果有人解释它,我将不胜感激。

最佳答案

Another way would be to have a union with fixed types and whenever it would need to use a constructor/destructor etc. a big switch would decide which union member to use depending on the index variable, but that would require a lot of code bloat and would only work for fixed types.

但这(道德上)对于任意类型列表都适用。一个switch具有连续非重叠case s 在逻辑上只是索引到程序代码中。因此您可以直观地替换 switch使用数组查找:提取 case你会switch进入 as 函数,然后 switch正在访问case s 表示索引数组并调用结果。

switch(tag) { // effectively "go to line here + tag", so conceptually a lot like indexing
case 0: ret = ...; break;
case 1: ret = ...; break;
...
}
// <=>
ret_t case0(args_t args) { return ...; }
ret_t case1(args_t args) { return ...; }
...
ret_t (*cases[])(args_t) = {case0, case1, ...};
ret = cases[tag](args); // and now it's literally indexing

但是生成一堆函数并根据模式构造表达式正是模板的作用。

template<std::size_t I, typename F, typename... Ts>
decltype(auto) visit_case(F &f, std::variant<Ts...> &v) {
    return f(std::get<I>(v));
}
// visit_case<0, F, T1, T2, ...>(f, v) assumes v is a T1 t1 and calls f(t1);
// visit_case<1, F, T1, T2, ...>(f, v) assumes v is a T2 t2 and calls f(t2);
// etc., so this template is capable of generating all the cases of a function on a variant as separate functions
template<typename F, typename... Ts, std::size_t... Is>
constexpr auto visit_cases_impl(std::index_sequence<Is...>) {
    return std::array{visit_case<Is, F, Ts...>...};
}
template<typename F, typename... Ts>
constexpr inline auto visit_cases =
    visit_cases_impl<F, Ts...>(std::index_sequence_for<Ts...>{});
// visit_cases<F, T1, T2, T3> =
// std::array{visit_case<0, F, T1, T2, T3>, visit_case<1, F, T1, T2, T3>, visit_case<2, F, T1, T2, T3>}

您遇到的“vtable ”指的是函数指针数组,表示如何处理可能的替代方案。这张表是您基本所需的全部 std::visit :

template<typename F, typename... Ts>
decltype(auto) visit(F f, std::variant<Ts...> &v) {
    return visit_cases<F, Ts...>[v.index()](f, v);
    // <=>
    // switch(v.index()) {
    // case 0: return f(std::get<0>(v));
    // case 1: return f(std::get<1>(v));
    // ...
    // }
}

现在,为了实现std::variant ,我认为需要有一些super_visit还将变体索引作为模板参数传递给 f ,但除此之外它应该相对简单。复制/移动构造函数/赋值和析构函数都可以编写为此类索引访问。 A complete example.

关于c++ - c++17 std::variant 如何确定使用哪种类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69332236/

相关文章:

c++ - 扫描线多边形填充算法

c++ - “GetConsoleWindow”未在此范围内声明?

c++ - 什么概念在这里起作用,它可以应用在哪里?

c++ - 随着 std::byte 的标准化,我们什么时候使用 void* 什么时候使用 byte*?

c++ - 如何使用抽象/接口(interface)类的 vector 作为函数参数?

c++ - 什么是完全限定名称?

C++17 内联变量与内联静态变量

c# - 如何在 C# 中优雅地搜索结构数组,从 C++ 移植?

C++ 基础 - If 语句测试

c++ - 检查 std::filesystem::path 是否在目录中