Search code examples
c++11templatestemplate-specializationtype-traitstemplate-argument-deduction

Specialization traits of nested struct


I have a template struct with a nested template struct.

template <int F>
struct foo
{
    template <int B>
    struct bar {
        static constexpr int f = F;
        static constexpr int b = B;
    };
 };

I want to create a trait like

template <class>
struct is_foo_bar : std::false_type { };

template <int F, int B>
struct is_foo_bar< foo<F>::bar<B> > : std::true_type { };

static_assert(is_foo_bar< foo<1>::bar<2> >::value);

This gives an error:

type/value mismatch at argument 1 in template parameter list for ‘template<class> struct is_foo_bar’<br>
struct is_foo_bar<foo<F>::bar<B>> : std::true_type { };

If I take bar out like

template <int F, int B>
struct foo_bar {
    static constexpr int f = F;
    static constexpr int b = B;
};

template <int F>
struct foo
{
    template <int B>
    using bar = foo_bar<F, B>;
};

template <class>
struct is_foo_bar : std::false_type { };

template <int F, int B>
struct is_foo_bar< foo_bar<F, B> > : std::true_type { };

static_assert(is_foo_bar< foo<1>::bar<2> >::value);

... it works. But this is not the way I want it to be. What I'm missing in code where bar declaration is in foo?


Solution

  • Using SFINAE it could be solved this way

    template <class, class = void>
    struct is_foo_bar : std::false_type { };
    
    template <class T>
    struct is_foo_bar< 
        T,
        std::enable_if_t<
            std::is_same_v< T, typename foo<T::f>::template bar<T::b> >
        >
    > : std::true_type { };
    

    This works, but you need to specify all arguments explicitly, which may be a problem (for ex. variadic arguments).

    EDIT: Found a soultion for variadic arguments

    template <int F>
    struct foo
    {
        template <int B, class... Params>
        struct bar {
            static constexpr int f = F;
            static constexpr int b = B;
            static constexpr size_t size = sizeof...(Params);
        };
    };
    
    template <class, class = void>
    struct is_foo_bar : std::false_type { };
    
    template <template <int, class...> class T, int B, class... Params>
    struct is_foo_bar<
        T<B, Params...>,
        std::enable_if_t<
            std::is_same_v<
                T<B, Params...>,
                typename foo< T<B, Params...>::f >::template bar<B, Params...>
            >
        >
    > : std::true_type { };
    
    template <int, class...>
    struct not_foo_bar {
        static constexpr int f = 0;
    };
    
    static_assert(is_foo_bar< foo<1>::bar<2> >::value);
    static_assert(is_foo_bar< foo<1>::bar<2, int> >::value);
    
    static_assert(not is_foo_bar< not_foo_bar<1> >::value);
    static_assert(not is_foo_bar< not_foo_bar<1, int> >::value);
    

    This makes sure bar belongs to foo. When there is no such requirement it should be less ugly to just test for existence of needed members with std::void_t and std::declval...