std::variant reflection. How can I tell which type of value std::variant is assigned?
Asked Answered
D

3

38

I have a class called foo_t that has a member called bar which could be any one of the types std::string, int, std::vector<double>, etc. I would like to be able to ask foo_t which type bar has been assigned to. I decided to use std::variant.

I've written a solution, but I'm not sure if this is a good use of std::variant. I'm not sure if it matters, but I expect the list of types to possibly grow much bigger in the future. I made an enum class to store which type std::variant is assigned to. My first implementation also available on wandbox:

#include <iostream>
#include <variant>
#include <vector>
#include <string>

enum foo_kind_t {
  double_list,
  name_tag,
  number,
  unknown
};

template <typename val_t>
struct get_foo_kind_t {
  constexpr static foo_kind_t value = unknown;
};

template <>
struct get_foo_kind_t<int> {
  constexpr static foo_kind_t value = number;
};

template <>
struct get_foo_kind_t<std::string> {
  constexpr static foo_kind_t value = name_tag;
};

template <>
struct get_foo_kind_t<std::vector<double>> {
  constexpr static foo_kind_t value = double_list;
};

class foo_t {

public:

  foo_t(): kind(unknown) {}

  template <typename val_t>
  void assign_bar(const val_t &val) {
    static_assert(get_foo_kind_t<val_t>::value != unknown, "unsupported assignment");
    kind = get_foo_kind_t<val_t>::value;
    bar = val;
  }

  foo_kind_t get_kind() {
    return kind;
  }

  template <typename val_t>
  val_t get_bar() {
    if (get_foo_kind_t<val_t>::value != kind) {
      throw std::runtime_error("wrong kind");
    }
    return std::get<val_t>(bar);
  }

private:

  foo_kind_t kind;

  std::variant<
    int,
    std::string,
    std::vector<double>
  > bar;

};

template <typename val_t>
void print_foo(foo_t &foo) {
    std::cout << "kind: " << foo.get_kind() << std::endl;
    std::cout << "value: " << foo.get_bar<val_t>() << std::endl << std::endl;
}

int main(int, char*[]) {

    // double_list
    foo_t b;
    std::vector<double> b_val({ 1.0, 1.1, 1.2 });
    b.assign_bar(b_val);
    std::cout << "kind: " << b.get_kind() << std::endl;
    std::cout << "value: vector size: " << b.get_bar<std::vector<double>>().size() << std::endl << std::endl;

    // name_tag
    foo_t d;
    std::string d_val("name");
    d.assign_bar(d_val);
    print_foo<std::string>(d);

    // number
    foo_t c;
    int c_val = 99;
    c.assign_bar(c_val);
    print_foo<int>(c);

    // unknown
    foo_t a;
    std::cout << a.get_kind() << std::endl;

    return 0;
}

Is this a good way to do it? Is there a way having better performance? Is there a way that requires less code to be written? Is there a way that doesn't require C++17?

Dobb answered 25/2, 2018 at 21:24 Comment(4)
Why do you need to know that?Anthologize
I have a tree that holds a bunch of nodes of type foo_t with each containing 0 or more of their own children of type foo_t. I would like to traverse this tree while drawing an user interface based on those nodes. I know I could use the visitor pattern for that, but I still want to be able to dynamically get the type.Dobb
std::variant::indexMerilyn
std::holds_alternativeIndividuality
S
1

There is a solution with type traits

    #include <iostream>
    #include <string>
    #include <type_traits>
    #include <variant>
    
    using MyVariant = std::variant<int, std::string>;
    
    enum class MyVariantType { integer, string };
    
    template <MyVariantType Type, typename T> struct is_variant_type : std::false_type {};
    
    template <> struct is_variant_type<MyVariantType::integer, int        > : std::true_type {};
    template <> struct is_variant_type<MyVariantType::string , std::string> : std::true_type {};
    
    template<MyVariantType VT>
    bool check_variant_type(const MyVariant& myvar)
    {
        return std::visit([&](const auto& arg) { 
            return is_variant_type<VT, std::decay_t<decltype(arg)>>::value;
        }, myvar);
    }
    
    int main(int argc, char* argv[])
    {
        MyVariant a = int(10);
        MyVariant b = "Hello";
    
        std::cout << check_variant_type<MyVariantType::integer>(a);
        std::cout << check_variant_type<MyVariantType::integer>(b);
    
        std::cout << check_variant_type<MyVariantType::string>(a);
        std::cout << check_variant_type<MyVariantType::string>(b);
    
        return 0;
    }
Stridor answered 16/9, 2022 at 13:7 Comment(0)
N
44

If you only need to ask "Is this variant of type X ?" for a single type X, then I recommend that you prefer std::holds_alternative over std::variant::index because the line of code is easier to read and will not have to be updated if the index of the type in the variant changes in the future.

Example:

if (std::holds_alternative<X>(my_variant)) {
    std::cout << "Variant is of type X" << std::endl;
}
Nomi answered 7/1, 2022 at 11:27 Comment(1)
Perfect! Much better then using index what has a lot of side effects when std::variant templates parameters will be changed.Hexateuch
D
23

Using std::variant::index to check stored type at runtime.

Dobb answered 21/3, 2018 at 3:26 Comment(0)
S
1

There is a solution with type traits

    #include <iostream>
    #include <string>
    #include <type_traits>
    #include <variant>
    
    using MyVariant = std::variant<int, std::string>;
    
    enum class MyVariantType { integer, string };
    
    template <MyVariantType Type, typename T> struct is_variant_type : std::false_type {};
    
    template <> struct is_variant_type<MyVariantType::integer, int        > : std::true_type {};
    template <> struct is_variant_type<MyVariantType::string , std::string> : std::true_type {};
    
    template<MyVariantType VT>
    bool check_variant_type(const MyVariant& myvar)
    {
        return std::visit([&](const auto& arg) { 
            return is_variant_type<VT, std::decay_t<decltype(arg)>>::value;
        }, myvar);
    }
    
    int main(int argc, char* argv[])
    {
        MyVariant a = int(10);
        MyVariant b = "Hello";
    
        std::cout << check_variant_type<MyVariantType::integer>(a);
        std::cout << check_variant_type<MyVariantType::integer>(b);
    
        std::cout << check_variant_type<MyVariantType::string>(a);
        std::cout << check_variant_type<MyVariantType::string>(b);
    
        return 0;
    }
Stridor answered 16/9, 2022 at 13:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.