Making type trait work for all derived types

122 Views Asked by At

I have a type trait and concept which checks if an std::variant can hold a given type T. Now I have a type variant2 which derives from std::variant, and I want to use that type trait with the new variant2 type. How can I accomplish this elegantly?

This doesn't work (Demo):

#include <variant>
#include <string>
#include <iostream>
#include <concepts>
#include <type_traits>

template<typename T, typename Variant>
struct variant_type;

template<typename T, typename... Args>
struct variant_type<T, std::variant<Args...>> 
    : public std::disjunction<std::is_same<T, Args>...> {};

template<typename T, typename Variant>
concept is_variant_type = variant_type<T, Variant>::value;

template <typename... Ts>
struct variant2 : public std::variant<Ts...> {
};

variant2<std::monostate, int, bool, std::string> var;

int main() {
    using T = std::string;

    if constexpr (is_variant_type<T, decltype(var)>) {
        std::cout << "Worked!" << std::endl;
    }
}

"Worked" never appears on the screen which is obvious because the default type trait is SFINAED.

2

There are 2 best solutions below

5
Oersted On BEST ANSWER

You can just fix your specialization by expressing that your template must inherit from std::variant

#include <concepts>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>

template <typename T, typename Variant>
struct variant_type;

template <typename T, template <typename...> typename Var, typename... Args>
struct variant_type<T, Var<Args...>>
    : public std::conjunction<
          std::disjunction<std::is_same<T, Args>...>,
          std::is_base_of<std::variant<Args...>, Var<Args...>>> {};

template <typename T, typename Variant>
concept is_variant_type = variant_type<T, Variant>::value;

template <typename... Ts>
struct variant2 : public std::variant<Ts...> {};

variant2<std::monostate, int, bool, std::string> var;

int main() {
    using T = std::string;

    if constexpr (is_variant_type<T, decltype(var)>) {
        std::cout << "Worked!" << std::endl;
    }
}

I just added a conjunction to achieve that and we'are done.
Live
[EDIT] sorry, it's just a bit more complicated: I added a template template parameter that implies that your inherited class has exactly the same template parameters than the base one, which might be restrictive.

0
康桓瑋 On

Since variant2 inherits std::variant, you can extract its base variant type through the following helper function

template<typename... Args>
auto as_variant(const std::variant<Args...>&) -> std::variant<Args...>;

template<typename T>
struct to_variant;

template<typename T>
  requires requires (const T& t) { as_variant(t); }
struct to_variant<T> {
  using type = decltype(as_variant(std::declval<const T&>()));
};

Then slightly modify the is_variant_type concept

template<typename T, typename Variant>
concept is_variant_type = 
  variant_type<T, typename to_variant<Variant>::type>::value;

Or more simply, just define is_variant_type directly as

template<typename T, typename Variant>
concept is_variant_type = requires (const Variant& var) {
  []<typename... Args>(const std::variant<Args...>&) 
    requires (std::same_as<T, Args> || ...)
  { }(var);
};

Demo