Search code examples
c++c++14sfinaereadabilityenable-if

Move SFINAE condition to the left-most for readability


Default template arguments are not considered when compiler judges whether there are some duplicate overload function.

This totally ruins my fun, while I try to push every dirty thing to left-most.

Here is an example.
Although the below code is not compilable, it is relatively beautiful and easy to read :-

template<bool enable= a&& b>  typename std::enable_if_t<enable,void> f(){}
template<bool enable= a&&!b>  typename std::enable_if_t<enable,void> f(){}
template<bool enable=!a&& b>  typename std::enable_if_t<enable,void> f(){}
template<bool enable=!a&&!b>  typename std::enable_if_t<enable,void> f(){}
                                                             //^ so neat
look like : if(enable){instantiate static f(){}} ... so intuitive! 

... compared to a compilable one :-

template<bool a1=a,bool b1=b> typename std::enable_if_t< a1&& b1,void> f(){}
template<bool a1=a,bool b1=b> typename std::enable_if_t< a1&&!b1,void> f(){}
template<bool a1=a,bool b1=b> typename std::enable_if_t<!a1&& b1,void> f(){}
template<bool a1=a,bool b1=b> typename std::enable_if_t<!a1&&!b1,void> f(){}
          ^ two temp "type"  ----> scroll ------->      ^ deep hidden logic

It may seem to be a trivial problem, but it reoccurs to me in many files.
It makes me nervous. I start to code SFINAE with fear.

Are there some ways to make it works and still be concise and intuitive?
Here is coliru demo.

Edit: Here is an example that is more similar to the real world case (beautiful-but-wrong version):-

template<class X,class A,class B,class C,class D>class Database{
    public: static constexpr bool hasC=typename X::hasC;             
    public: static constexpr bool hasD=typename X::hasD;    
    /** some complex field (NOT depend on "hasC" and "hasD") */    
    public: template<bool enable=!hasC&&!hasD>      
      std::enable_if_t<enable,void> add(A a,B b){    
         /**some complex */
    }
    public: template<bool enable=hasC&&!hasD>      
      std::enable_if_t<enable,void> add(A a,B b,C c){    
         /**some complex */
    }
    public: template<bool enable=!hasC&&hasD>      
      std::enable_if_t<enable,void> add(A a,B b,D d){    
         /**some complex */
    }
    public: template<bool enable=hasC&&hasD>      
      std::enable_if_t<enable,void> add(A a,B b,C c,D d){    
         /**some complex */
    }
}; 

Solution

  • I think that until you get constexpr if with C++17, the most legible way to do this is with tagged dispatch:

    template<bool a,bool b>
    class Test{
    public:
        void f()
        {
            fhelp(f_tag<>{});
        }
    private:
        template<bool = a, bool = b>
        struct f_tag{};
    
        void  fhelp(f_tag<true, true>){}
        void  fhelp(f_tag<true, false>){}
        void  fhelp(f_tag<false, true>){}
        void  fhelp(f_tag<false, false>){}
    };
    

    That way we can be more explicit about what the expected values of a and b are. This works well especially since your conditionals are all &&. It's also more obvious to the naked eye that no two overloads are the same.

    Demo


    Edit

    The C++17 version would have us write our f() like so:

    void f()
    {
        if constexpr (a && b)
        {
             // ...
        }
        if constexpr (a && !b)
        {
            // ...
        }
        if constexpr (!a && b)
        {
            // ...
        }
        if constexpr (!a && !b)
        {
            // ...
        }
    }
    

    In fact this may not be very legible after all. Personally I still prefer the tagged dispatch approach.


    Edit2:

    Regarding your "real-world" example where the functions have different parameters, you may start to consider specializing the class instead. However, you can still accomplish what you need with tag dispatch. The public-facing function needs to become a variadic template, while the helper functions retain their true types, so you're still safe:

    public: 
        template<bool = X::hasC, bool = X::hasD>
        struct add_tag{};
    
        template<class... T>
        void add(T&&... args)
        {
            add_help(add_tag<>{}, std::forward<T>(args)...);
        }
    
    private:
        void add_help(add_tag<false, false>, A a, B b)
        {/*..*/}
        void add_help(add_tag<true, false>, A a, B b, C c)
        {/*..*/}
        void add_help(add_tag<false, true>, A a, B b, D d)
        {/*..*/}
        void add_help(add_tag<true, true>, A a, B b, C c, D d)
        {/*..*/}
    

    Demo2