28

I have a class with an std::variant in it. This std::variant type is only allowed to hold a specific list of types.

I have template functions which allow the user of the class to insert various values into an std::unordered_map, the map holds values of this variant type. I.e., the user is only allowed to insert values if it's type is in the specific list of types. However, I don't want the user to be able to define this list of types themselves.

class GLCapabilities
{
public:
    using VariantType = std::variant<GLint64>;  // in future this would have other types

    template <typename T>
    std::enable_if_t<???> AddCapability(const GLenum parameterName)
    {
        if(m_capabilities.count(parameterName) == 0)
        {
            /*... get correct value of type T ... */
            m_capabilities.insert(parameterName,value);
        }
    }

    template<typename T>
    std::enable_if_t<???,T> GetCapability(const GLenum parameterName) const
    {
        auto itr = m_capabilities.find(parameterName);
        if(std::holds_alternative<T>(*itr))
            return std::get<T>(*itr);

        return T;
    }

private:
    std::unordered_map<GLenum,VariantType> m_capabilities;
};

You'll see in the above where there's the ???, how can I check? Some combination of std::disjunction with std::is_same?

Like

std::enable_if<std::disjunction<std::is_same<T,/*Variant Types???*/>...>>

To make it clear, I'd prefer to not have to check against each allowed type manually.

NeomerArcana
  • 1,978
  • 3
  • 23
  • 50
  • Wouldn't `m_capabilities.insert(` simply fail to compile is an invalid value is passed? It would seem to me that simple duck typing is the easy answer here. It would be a different matter if you used `emplace()`, obviously, or do you want to prevent any implicit casting whatsoever? –  Aug 26 '17 at 05:31
  • Because I'll be using various OpenGL types, like `GLuint` or `GLint` etc, implicit casting could be a problem; so yes, I'd like to avoid it. – NeomerArcana Aug 26 '17 at 05:35
  • In Boost.Variant we had `VariantType::types` to get an `mpl::list` of types. Then you could use `mpl::contains`. That seems to be absent in `std::variant`, one has to implement it by deducing the template parameters and using variadic templates. – alfC Aug 26 '17 at 06:02
  • Can someone explain `std::enable_if` to me? I don't get it – Post Self Aug 26 '17 at 17:24

6 Answers6

27

Edit: I actually dig your std::disjunction idea, and it absolutely works. You just have to extract the type list using template specialization.

My entire old-school recursive mess becomes simply:

template<typename T, typename VARIANT_T>
struct isVariantMember;

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

Original answer: Here's a simple template that accomplishes this. It works by returning false for empty type lists. For non-empty lists, it returns true if the first type passes std::is_same<>, and recursively invokes itself with all but the first type otherwise.

#include <vector>
#include <tuple>
#include <variant>

// Main lookup logic of looking up a type in a list.
template<typename T, typename... ALL_T>
struct isOneOf : public std::false_type {};

template<typename T, typename FRONT_T, typename... REST_T>
struct isOneOf<T, FRONT_T, REST_T...> : public 
  std::conditional<
    std::is_same<T, FRONT_T>::value,
    std::true_type,
    isOneOf<T, REST_T...>
  >::type {};

// Convenience wrapper for std::variant<>.
template<typename T, typename VARIANT_T>
struct isVariantMember;

template<typename T, typename... ALL_T>
struct isVariantMember<T, std::variant<ALL_T...>> : public isOneOf<T, ALL_T...> {};

// Example:
int main() {
  using var_t = std::variant<int, float>;

  bool x = isVariantMember<int, var_t>::value; // x == true
  bool y = isVariantMember<double, var_t>::value; // y == false

  return 0;
}

N.B. Make sure you strip cv and reference qualifiers from T before invoking this (or add the stripping to the template itself). It depends on your needs, really.

6

Since you're already using C++17, fold-expressions make this easier:

template <class T, class U> struct is_one_of;

template <class T, class... Ts> 
struct is_one_of<T, std::variant<Ts...>>
: std::bool_constant<(std::is_same_v<T, Ts> || ...)>
{ };

For added readability, you could add an alias in your class:

class GLCapabilities
{
public:
    using VariantType = std::variant<GLint64>;  // in future this would have other types
    template <class T> using allowed = is_one_of<T, VariantType>;

    template <typename T>
    std::enable_if_t<allowed<T>{}> AddCapability(const GLenum parameterName)
    { ... }
};
Barry
  • 286,269
  • 29
  • 621
  • 977
  • Thanks, this is very handy. Weirdly enough, in my case it didn't compile as is (MSVC 16.7.3, C++17 enabled). I had to change the enable_if like that : std::enable_if_t::value> Not sure why though ! – Scylardor Dec 08 '21 at 23:36
5
template <class T> struct type {};
template <class T> constexpr type<T> type_v{};

template <class T, class...Ts, template<class...> class Tp>
constexpr bool is_one_of(type<Tp<Ts...>>, type<T>) {
    return (std::is_same_v<Ts, T> || ...); 
}

Then use is_one_of(type_v<VariantType>, type_v<T>) in the enable_if.

T.C.
  • 133,968
  • 17
  • 288
  • 421
2

You can avoid using std::enable_if_t and use instead a classic decltype-based SFINAE expressions like the one in the following example:

#include<variant>
#include<utility>

struct S {
    using type = std::variant<int, double>;

    template<typename U>
    auto f()
    -> decltype(std::declval<type>().emplace<U>(), void()) {
        // that's ok
    }
};

int main() {
    S s;
    s.f<int>();
    //s.f<char>();
}

If you toggle the comment to the last line, you get a compile-time time error for char isn't a type accepted by your variant.

The pros of this solution is that it's simple and you don't have neither to include type_traits (granted, you have to include utility) nor to use a support class to get a bool value to test out of it.
Of course, you can adjust the return type accordingly with your requirements for each function.

See it up and running on wandbox.


Otherwise, if you can stick with the limitations of std::holds_alternative (the call is ill-formed if the type compares more than once in the parameters list of the variant), note that it's a constexpr function and it just does what you want:

#include<type_traits>
#include<variant>
#include<utility>

struct S {
    using type = std::variant<int, double>;

    template<typename U>
    std::enable_if_t<std::holds_alternative<U>(type{})>
    f() {
        // that's ok
    }
};

int main() {
    S s;
    s.f<int>();
    //s.f<char>();
}

As above, toggle the comment and you'll get a compile-time error as expected.

See it up and running on wandbox.

skypjack
  • 49,335
  • 19
  • 95
  • 187
2
#include <type_traits>
#include <variant>


template <typename, typename... T0ToN>
struct is_one_of;

template <typename T>
struct is_one_of<T> : public std::false_type
{
};

template <typename T, typename... T1toN>
struct is_one_of<T, T, T1toN...> : public std::true_type
{
};

template <typename T, typename P, typename... T1toN>
struct is_one_of<T, P, T1toN...> : is_one_of<T, T1toN...>
{
};

template <typename Type, typename ... Others>
struct is_in_variant : public std::false_type {};

template <typename Type, typename ... Others>
struct is_in_variant<Type, std::variant<Others...>> : public is_one_of<Type, Others...>
{};


int main()
{
    std::variant<int, float> v;
    return is_in_variant<double, std::variant<int, float>>::value ? 4 : 8;
}
apramc
  • 1,346
  • 1
  • 15
  • 30
0

You can try using SFINAE by constructing the VariantType from the T type.

template <typename T, typename = VariantType(std::declval<T>())>
void AddCapability(T const& t); // not sure how you want to use it.

Or use std::is_constructible<VariantType, T>. After all you probably want to know if you can assign/initialize from the type, not if type is actually one of the variant types (which is more restrictive).

alfC
  • 14,261
  • 4
  • 67
  • 118
  • Implicit conversions will kick in, which the OP explicitely wants to avoid. –  Aug 26 '17 at 05:53
  • @Frank. Ok, yes, it wasn't clear from the question. I guess that is an unnecessary restriction. The OP should reconsider that arbitrary restriction. – alfC Aug 26 '17 at 05:56
  • You should read the comment chain in the OP, which goes over why he needs it. –  Aug 26 '17 at 05:58