简体   繁体   English

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

[英]How can I code something like a switch for std::variant?

I have some var = std::variant<std::monostate, a, b, c> when a, b, c is some types.a, b, c是某些类型时,我有一些var = std::variant<std::monostate, a, b, c>

How, at runtime, do I check what type var contains?如何在运行时检查var包含什么类型?

In the official documentation I found information that if var contains a type and I write std::get<b>(var) I get an exception.在官方文档中,我发现如果var包含a类型并且我写std::get<b>(var)我会得到一个异常。 So I thought about this solution:所以我想到了这个解决方案:

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
    }
  }
}

But it's so complicated and ugly!但它是如此复杂和丑陋! Is there a simpler way to check what type std::variant contains?有没有更简单的方法来检查std::variant包含什么类型?

std::visit is the way to go: std::visit是通往 go 的途径:

There is even overloaded to allow inlined visitor:甚至还有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...>;

and so:所以:

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

To use chained if-branches instead, you might used std::get_if要改为使用链式 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
  // ...
}

The most simple way is to switch based on the current std::variant::index() .最简单的方法是根据当前的std::variant::index()进行switch This approach requires your types ( std::monostate , A , B , C ) to always stay in the same order.这种方法要求您的类型( std::monostate monostate 、 ABC )始终保持相同的顺序。

// 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;
    }

    }
}

If your callable works with any type, you can also use std::visit :如果您的可调用对象适用于任何类型,您还可以使用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);
}

If you don't want std::visit to accept std::monostate , then just check if the index is 0. Once again, this relies on std::monostate being the first type of the variant, so it is good practice to always make it the first.如果您不希望std::visit接受std::monostate ,则只需检查index是否为 0。再一次,这依赖于std::monostate作为变体的第一种类型,因此最好是永远把它放在第一位。

You can also detect the type using if-constexpr inside the callable.您还可以在可调用对象中使用if-constexpr来检测类型。 With this approach, the arguments don't have to be in the same order anymore:使用这种方法,arguments 不必再按相同的顺序排列:

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

Note that the first lambda returns void because it just processes the current value of the variant.请注意,第一个 lambda 返回void ,因为它只处理变量的当前值。 If you want to modify the variant, your lambda needs to return my_variant again.如果你想修改变体,你的lambda需要再次返回my_variant

You could use an overloaded visitor inside std::visit to handle A or B separately.您可以在std::visit中使用重载访问者来分别处理AB See std::visit for more examples.有关更多示例,请参见std::visit

You can use standard std::visit您可以使用标准std::visit

Usage example:使用示例:

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

Well, with some macro magic, you can do something like:好吧,借助一些宏魔法,您可以执行以下操作:

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

#define __X_CONCAT_1(x,y) x ## y
#define __X_CONCAT(x,y) __X_CONCAT_1(x,y)

template <typename T>
struct __helper {  };

// extract the type from a declaration
// we use function-type magic to get that: typename __helper<void ( (declaration) )>::type
// declaration is "int &x" for example, this class template extracts "int"
template <typename T>
struct __helper<void (T)> {
    using type = std::remove_reference_t<T>;
};

#define variant_if(variant, declaration) \
    if (bool __X_CONCAT(variant_if_bool_, __LINE__) = true; auto * __X_CONCAT(variant_if_ptr_, __LINE__) = std::get_if<typename __helper<void ( (declaration) )>::type>(&(variant))) \
        for (declaration = * __X_CONCAT(variant_if_ptr_, __LINE__); __X_CONCAT(variant_if_bool_, __LINE__);  __X_CONCAT(variant_if_bool_, __LINE__) = false)

#define variant_switch(variant) if (auto &__variant_switch_v = (variant); true)
#define variant_case(x) variant_if(__variant_switch_v, x)

int main() {
    std::variant<int, long> v = 12;
    std::variant<int, long> w = 32l;

    std::cout << "variant_if test" << std::endl;

    variant_if(v, int &x) {
        std::cout << "int = " << x << std::endl;
    }
    else variant_if(v, long &x) {
        std::cout << "long = " << x << std::endl;
    }

    std::cout << "variant_switch test" << std::endl;

    variant_switch(v) {
        variant_case(int &x) {
            std::cout << "int = " << x << std::endl;

            variant_switch (w) {
                variant_case(int &x) {
                    std::cout << "int = " << x << std::endl;
                }

                variant_case(long &x) {
                    std::cout << "long = " << x << std::endl;
                }
            }
        };

        variant_case(long &x) {
            std::cout << "long = " << x << std::endl;

            variant_switch (w) {
                variant_case(int &x) {
                    std::cout << "int = " << x << std::endl;
                }

                variant_case(long &x) {
                    std::cout << "long = " << x << std::endl;
                }
            }
        };
    }

    return 0;
}

I tested this approach with GCC and Clang, no guarantees for MSVC.我用 GCC 和 Clang 测试了这种方法,不保证 MSVC。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM