Is it possible to define a function which provides inspection of its templated argument with a specialization for std::variant
, without actually including <variant>
? Of course for the implementation to be meaningful, the variant type will need to be complete, but the question is about defining such a function before using it.
SFINAE can be used to detect variant-likeness so some std::enable_if
or if constexpr
construct could be used to define a condition to branch on:
Note: The type trait is indeed a heuristic, because I do not wish it necessary for <variant>
to be included for just the definition.
#include <type_traits>
template<typename T, typename = void> struct is_variant : std::false_type {};
template<typename T> struct is_variant<T, std::void_t<decltype(std::declval<T>().valueless_by_exception())>> : std::true_type {};
template<typename T>
int get_size_if_variant() {
if constexpr (is_variant<T>::value) {
return std::variant_size_v<T>; // fails to compile: 'variant_size_v' is not a member of 'std'
}
return -1;
}
#include <variant>
#include <iostream>
int main() {
std::cout << get_size_if_variant<std::variant<int, float, std::string>>() << std::endl;
std::cout << get_size_if_variant<bool>() << std::endl;;
return 0;
}
Is this possible at all with C++17?
If you for some reason don't want to #include<variant>
before creating a type trait and a function that uses definitions in that header file, you could get around it by creating another new trait. I wouldn't call the first trait is_variant
because that's not what it's checking, but I'll assume that name will be used here.
You could create a trait that just return the template parameter count:
// you could leave the primary unimplemented to get a compilation
// error if used with a T that's not based on a template
inline constexpr auto not_a_template = static_cast<std::size_t>(-1);
template <class T> // primary
struct template_argument_count
: std::integral_constant<std::size_t, not_a_template> {};
template <template <class...> class T, class... Args>
struct template_argument_count<T<Args...>>
: std::integral_constant<std::size_t, sizeof...(Args)> {};
template <class T>
inline constexpr std::size_t template_argument_count_v =
template_argument_count<T>::value;
Then your function could be:
inline constexpr auto not_a_variant = static_cast<std::size_t>(-1);
template<class T>
std::size_t get_size_if_variant() {
if constexpr (is_variant<T>::value) {
return template_argument_count_v<T>; // new trait used here
}
return not_a_variant;
}