Search code examples
c++templatesvariant

how to use is_invocable with variants and std::holds_alternative


If I have a variant Value:

using Value = std::variant<bool, float>;

Then std::holds_alternative() should be callable with those types but not others:

Value v;
if (std::holds_alternative<bool>(v))... // compile ok
if (std::holds_alternative<float>(v))... // compile ok
if (std::holds_alternative<int>(v))... // compile error

Given a type T, how can I check if std::holds_alternative<T>(const Value&) is callable? I want to do something like this

template<class T>
void check() {
    if (std::is_invocable_v<decltype(std::holds_alternative<bool>), const Value&>) {
        std::cout << "callable";
    } else {
        std::cout << "not callable";
    }
}

But this doesn't compile because I think it instantiates std::holds_alternative<bool> without considering the Value part. What's the correct syntax here? Thanks


Solution

  • Sadly, std::holds_alternative isn't SFINE-friendly. All instantiations of std::holds_alternative are invokable, but they contain a static_assert that prevents the calls from compiling if its requirements aren't met.

    That means there's no good way to write your check function in terms of std::holds_alternative directly. Instead, you can implement it in terms of std::holds_alternative's underlying requirement that the given type appears exactly once in the variant's type list:

    template <typename T, typename... Ts>
    constexpr std::size_t count_in_pack()
    {
        std::array<bool, sizeof...(Ts)> a = { std::is_same_v<T, Ts>... };
        return std::count(a.begin(), a.end(), true);
    }
    
    template <typename... Ts>
    void check(const std::variant<Ts...>&)
    {
        if (count_in_pack<bool, Ts...>() == 1) {
            std::cout << "callable\n";
        } else {
            std::cout << "not callable\n";
        }
    };
    

    Demo

    Or, if you want to be able to call check with just the type, and no actual instance of a std::variant then you can use a helper class with partial specialization:

    template <typename T, typename... Ts>
    constexpr std::size_t count_in_pack()
    {
        std::array<bool, sizeof...(Ts)> a = { std::is_same_v<T, Ts>... };
        return std::count(a.begin(), a.end(), true);
    }
    
    template <typename T, typename V>
    struct CanCallHoldsAlternative
        : std::false_type
    {};
    
    template <typename T, typename... Vs>
    struct CanCallHoldsAlternative<T, std::variant<Vs...>>
        : std::conditional_t<
            count_in_pack<T, Vs...>() == 1,
            std::true_type,
            std::false_type
        >
    {};
    
    template <typename T>
    void check()
    {
        if (CanCallHoldsAlternative<bool, T>::value) {
            std::cout << "callable\n";
        } else {
            std::cout << "not callable\n";
        }
    };
    

    Demo