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