Search code examples
c++c++17template-meta-programming

Constexpr-check using SFINAE for std::variant


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?


Solution

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

    Demo