32

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?

dhoodlum
  • 1,103
  • 2
  • 9
  • 18
  • 2
    Why do you need to know that? – juanchopanza Feb 25 '18 at 21:26
  • 2
    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. – dhoodlum Feb 25 '18 at 21:33
  • 4
    [`std::variant::index`](http://en.cppreference.com/w/cpp/utility/variant/index) – Justin Feb 25 '18 at 22:03
  • 7
    std::holds_alternative – Vencat Nov 23 '20 at 09:40

3 Answers3

29

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;
}
Gabriel Devillers
  • 3,155
  • 2
  • 30
  • 53
  • Perfect! Much better then using index what has a lot of side effects when std::variant templates parameters will be changed. – slinkin Jun 09 '22 at 09:39
22

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

dhoodlum
  • 1,103
  • 2
  • 9
  • 18
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;
    }
Ivan Bk
  • 26
  • 2