c++templatesc++14variadic-templatestemplate-meta-programming

C++ variadic template partial template specialization with std::enable_if


My question is how can I use std::enable_if in variadic template partial template specialization scenario?

For example I have a class that use variadic template partial specialization like below

        /**
         *  Common case.
         */
        template<typename... Args>
        struct foo;

        /**
         *  Final superclass for foo.
         */
        template<>
        struct foo<>{ void func() {} };

        /**
         *  Regular foo class.
         */
        template<typename H, typename... T>
        struct foo<H, T...> : public foo<T...> {
            typedef super foo<T...>;
            void func() {
                cout << "Hi" << endl;
                this->super::template func();
            }
        }

It works fine, but I want a specific partial specialization if H is integral type, so I added new code like below

        enum class enabled {_};
        template<typename H, typename... T>
        struct foo<H, typename std::enable_if<std::integral_type<H>::value,enabled>::type = enabled::_, T...> : public foo<T...> {
            typedef super foo<T...>;
            void func() {
                cout << "Hooray Inegral" << endl;
                this->super::template func();
            }
        }

But the above code does not work, my question is how to do it like above?


Solution

  • enable_if<bool b, T=void> is a class template that defines type=T if b==true. So, enable_if<b>::type is valid expression only if b==true. SFINAE states that a substitution failure is not an error. IMHO not_disable_if would be more fitting name.

    Partial specialization works by pattern matching templates against the currently resolved type. You can't add new template arguments into it because it will match something different. struct foo<H,std::enable_if_t<...,void>> only matches foo<H,void> if ... is deductible from H and evaluates to true.

    struct foo<std::enable_if_t<std::is_integral_v<H>,H>> cannot work simply because there's no way to deduce H from e.g. foo<int>. It would have to somehow deduce the semantics of enable_if and is_integral and see that if H=int, then it resolves to exactly foo<int> which of course cannot be done in general.

    SFINAE can only be only applied to those parts that are considered during overload resolution. The first one and the one you used is class template arguments but as I stated above that would change what it actually matches. The other option are template parameters themselves. But C++ disallows default template arguments for class specialization which are used usually for that. There's no return value as in functions. SFINAE doesn't use anything inside the class body or its base classes. I don't think that what you want is with your current setup possible.

    I will offer a slight redesign:

    #include <iostream>
    #include <type_traits>
    
    // Only specifies behaviour for the head part - one T
    // Ts... can be ignored, but are required.
    //  - I left them because I'm not sure whether you want to use them in the real code.
    //  - But they are required because `foos` use them as base classes and they must be unique.
    template<typename T,bool is_integral,typename...Ts>
    struct foo_impl;
    
    template<typename T,typename...Ts>
    struct foo_impl<T,true,Ts...>
    {
        void func() {
            std::cout << "Hooray Inegral" << std::endl;
        }
    };
    
    template<typename T,typename...Ts>
    struct foo_impl<T,false,Ts...>
    {
        void func() {
            std::cout << "Hi" << std::endl;
        }
    };
    
    template<typename T,typename...Ts>
    using foo = foo_impl<T,std::is_integral<T>::value,Ts...>;
    
    template<typename...Ts>
    struct foos;
    
    template<typename H,typename...Ts>
    struct foos<H,Ts...> : private foo<H,Ts...>, public foos<Ts...>
    {
       using head = foo<H,Ts...>;
       using tail = foos<Ts...>;
       //Only named differently for clarity, feel free to rename it back to 'func'
       void rec_func()
       {
           //If we inherited from foo<H> so this was only this->foo<H>::func() then
           //foos<int,int> would have two foo<int> base classes and this call would be ambigious.
           this->head::func();
           this->tail::rec_func();
       }
    };
    
    template<> struct foos<>{
        void rec_func(){}
    };
    
    int main()
    {
        foos<int,int,char,double> x;
        x.rec_func();
    }
    

    The foo only deals with one T and has required specializations. You can extract any common behaviour between foo<T,false> and foo<T,true> into a common base class. Currently there's a duplication between foo and foos API. But foo' API can be treated as private and can be different, so I wouldn't say that's downside at all. As I explained Ts... in foo_impl is required. If you don't need them they can be removed by - e.g. by simply deriving from std::tuple<foo<Ts>...> and some fold expressions(C++17)/magic(c++14) to call func functions. I can add that if you want.