20

我有一些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
    }
  }
}

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

4

3 回答 3

17

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...>;

所以:

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

要改为使用链式 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
  // ...
}
于 2020-08-19T08:50:43.217 回答
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;
    }

    }
}

如果您的可调用对象适用于任何类型,您还可以使用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);
}

如果您不想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);
}

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

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

于 2020-08-19T07:47:00.177 回答
2

您可以使用标准std::visit

使用示例:

#include <variant>
#include <iostream>
#include <type_traits>

struct a {};
struct b {};
struct c {};

int main()
{
   std::variant<a, b, c> var = a{};

   std::visit([](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, a>)
                std::cout << "is an a" << '\n';
            else if constexpr (std::is_same_v<T, b>)
                std::cout << "is a b" << '\n';
            else if constexpr (std::is_same_v<T, c>)
                std::cout << "is a c" << '\n';
            else 
               std::cout << "is not in variant type list" << '\n';
        }, var);
}
于 2020-08-19T07:42:31.313 回答