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