3

Consider the following function for determining if variables of identical variant types are holding the same type:

#include <variant>
#include <iostream>

template <typename... Ts>
bool hold_same_types(const std::variant<Ts...>& v1, const std::variant<Ts...>& v2) {
    return ((std::holds_alternative<Ts>(v1) && std::holds_alternative<Ts>(v2)) || ...);
}

struct foo {};
struct bar {};
struct quux {};
struct mumble {};

using var1 = std::variant<foo, bar, quux>;

int main()
{
    var1 b1 = bar{};
    var1 b2 = bar{};
    var1 q = quux{};

    std::cout << "(b1 , b2)  => " << hold_same_types(b1, b2) << "\n";
    std::cout << "(b1 , q)  => " << hold_same_types(b1, q) << "\n";
}

There seems to be no easy way to extend hold_same_types to handle heterogenous variants. For example, the following doesn't work

//...
using var1 = std::variant<foo, bar, quux>;
using var2 = std::variant<bar, quux, mumble>;

template <typename... Ts, typename... Us>
bool hold_same_types(const std::variant<Ts...>& v1, const std::variant<Us...>& v2) {
    return ((std::holds_alternative<Ts>(v1) && std::holds_alternative<Ts>(v2)) || ...);
}

int main()
{
    var1 b1 = bar{};
    var2 m = mumble{};

    std::cout << "(b1 , m)  => " << hold_same_types(b1, m) << "\n";
}

because

  1. You can't use multiple parameter packs like that.

  2. Even if you could get around that, the fold expression will not compile if Ts... contains a type that is not in Us....

That is, std::holds_alternative<T>(some_variant) will not return false if T is not an alternative in some_variant, it won't even compile.

I've tried some more complicated approaches, but I couldn't get them to work. The main problem I run into is generally not being able to use two parameter packs.

jwezorek
  • 8,592
  • 1
  • 29
  • 46

2 Answers2

4

For two variants of the same type, you can just compare their .index().

For different variants, you can use std::visit. It can accept more than one variant at a time:

template <typename A, typename B>
constexpr bool hold_same_type(const A &a, const B &b)
{
    return std::visit(
        [](const auto &a, const auto &b){return std::is_same_v<decltype(a), decltype(b)>;},
        a, b
    );
}

This instantiates the lambda M*N times, which may or may not be a bad idea for large variants.

As an alternative, you can write a function that returns const std::type_info & for the type stored in a single variant (again using std::visit), then compare the results for two variants. This is left as an exercise to the reader.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
1
  1. You can change the template parameters of your hold_same_types to template <typename Variant, typename... Ts>, because it suffices to check all types of one variant type with the other variant. If the other variant does not have the type, it will definitely not old the same type.
  2. You could write your own my_holds_alternative implementation, that returns false if the type is not included in the variant, instead of a failing static_assert.
#include <variant>
#include <iostream>

template <typename Variant, typename T, size_t... I>
constexpr bool has_type_impl(std::index_sequence<I...>)
{
    return (std::is_same_v<std::variant_alternative_t<I, Variant>, T> || ...);
}

template <typename Variant, typename T>
constexpr bool has_type()
{
    return has_type_impl<Variant, T>(std::make_index_sequence<std::variant_size_v<Variant>>());
}

template <typename T, typename Variant>
constexpr bool my_holds_alternative(Variant const& v)
{
    if constexpr (has_type<Variant, T>())
    {
        return std::holds_alternative<T>(v);
    } else {
        return false;
    }
}

template <typename Variant, typename... Ts>
constexpr bool hold_same_types(const Variant& v1, const std::variant<Ts...>& v2)
{
    return ((my_holds_alternative<Ts>(v1) && my_holds_alternative<Ts>(v2)) || ...);
}

struct foo {};
struct bar {};
struct quux {};
struct mumble {};

using var1 = std::variant<foo, bar, quux>;
using var2 = std::variant<bar, quux, mumble>;

int main()
{
    var1 b1 = bar{};
    var1 b2 = bar{};
    var1 q = quux{};

    std::cout << "(b1 , b2)  => " << hold_same_types(b1, b2) << "\n";
    std::cout << "(b1 , q)  => " << hold_same_types(b1, q) << "\n";

    var2 m = mumble{};

    std::cout << "(b1 , m)  => " << hold_same_types(b1, m) << "\n";
}
(b1 , b2)  => 1
(b1 , q)  => 0
(b1 , m)  => 0

https://godbolt.org/z/Ej1adsfqM

joergbrech
  • 2,056
  • 1
  • 5
  • 17