我如何编写类似 std::variant 开关的代码?

das*_*fex 19 c++ c++17 std-variant

我有一些var = std::variant<std::monostate, a, b, c>什么时候a, b, c是一些类型。

如何在运行时检查var包含什么类型?

在官方文档中,我发现如果var包含a类型并且我写的信息,std::get<b>(var)我会得到一个异常。所以我想到了这个解决方案:

try {
  std::variant<a>(var);
  // Do something
} catch(const std::bad_variant_access&) {
  try {
    std::variant<b>(var);
    // Do something else
  } catch(const std::bad_variant_access&) {
    try {
     std::variant<c>(var);
     // Another else
    } catch (const std::bad_variant_access&) {
      // std::monostate
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

但它是如此复杂和丑陋!有没有更简单的方法来检查std::variant包含什么类型?

Jar*_*d42 15

std::visit 是要走的路:

甚至overloaded允许内联访问者:

// helper type for the visitor #4
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
// explicit deduction guide (not needed as of C++20)
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
`overloaded`
Run Code Online (Sandbox Code Playgroud)

所以:

std::visit(overloaded{
  [](std::monostate&){/*..*/},
  [](a&){/*..*/},
  [](b&){/*..*/},
  [](c&){/*..*/}
}, var);
Run Code Online (Sandbox Code Playgroud)

要改用链接的 if 分支,您可能会使用 std::get_if

if (auto* v = std::get_if<a>(var)) {
  // ...
} else if (auto* v = std::get_if<b>(var)) {
  // ...
} else if (auto* v = std::get_if<c>(var)) {
  // ...
} else { // std::monostate
  // ...
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*tke 13

最简单的方法是switch基于当前 std::variant::index(). 这种方法要求您的类型 ( std::monostate, A, B, C) 始终保持相同的顺序。

// I omitted C to keep the example simpler, the principle is the same
using my_variant = std::variant<std::monostate, A, B>;

void foo(my_variant &v) {
    switch (v.index()) {

    case 0: break; // do nothing because the type is std::monostate

    case 1: {
        doSomethingWith(std::get<A>(v));
        break;
    }

    case 2: {
        doSomethingElseWith(std::get<B>(v));
        break;
    }

    }
}
Run Code Online (Sandbox Code Playgroud)

如果您的可调用对象适用于任何类型,您还可以使用std::visit

void bar(my_variant &v) {
    std::visit([](auto &&arg) -> void {
        // Here, arg is std::monostate, A or B
        // This lambda needs to compile with all three options.
        // The lambda returns void because we don't modify the variant, so
        // we could also use const& arg.
    }, v);
}
Run Code Online (Sandbox Code Playgroud)

如果您不想std::visit接受std::monostate,则只需检查 是否index为 0。再一次,这依赖于std::monostate变体的第一种类型,因此始终将其设为第一种是一种很好的做法。

您还可以使用if-constexpr内部调用检测类型。使用这种方法,参数不必再按相同顺序排列:

void bar(my_variant &v) {
    std::visit([](auto &&arg) -> my_variant { 
        using T = std::decay_t<decltype(arg)>;
        if constexpr (std::is_same_v<std::monostate, T>) {
            return arg; // arg is std::monostate here
        }
        else if constexpr (std::is_same_v<A, T>) {
            return arg + arg; // arg is A here
        }
        else if constexpr (std::is_same_v<B, T>) {
            return arg * arg; // arg is B here
        }
    }, v);
}
Run Code Online (Sandbox Code Playgroud)

请注意,第一个 lambda 返回,void因为它只处理变体的当前值。如果您想修改变体,您的 lambda 需要my_variant再次返回。

您可以在内部使用重载访问者std::visit来处理AB单独处理。有关std::visit更多示例,请参见。