将 SFINAE 条件移到最左侧以便于阅读

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.

这完全毁了我的乐趣,而我试图把所有脏东西都推到最左边。

这是一个例子。
虽然下面的代码不可编译,但它相对漂亮且易于阅读:-

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! 

...与可编译的相比:-

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

这似乎是一个微不足道的问题,但它在我的许多文件中反复出现。
这让我很紧张。我开始恐惧地编写 SFINAE。

是否有一些方法可以让它工作并且仍然简洁直观?
这里是 coliru demo.

编辑: 这是一个更接近真实世界案例的例子(美丽但错误的版本):-

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 */
    }
}; 

我认为,在使用 C++17 获得 constexpr if 之前,最清晰的方法是使用标记调度:

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>){}
};

这样我们就可以更明确地了解 ab 的预期值是什么。这很有效,尤其是因为您的条件都是 &&。肉眼也更明显,没有两个重载是相同的。

Demo


编辑

C++17 版本会让我们这样写 f()

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

事实上,这毕竟可能不是很清晰。我个人还是更喜欢tagged dispatch方式


编辑2:

关于函数具有不同参数的 "real-world" 示例,您可能会开始考虑专门化 class。但是,您仍然可以通过标签分派完成您需要的工作。 public-facing 函数需要成为一个可变参数模板,而辅助函数保留它们的真实类型,所以你仍然安全:

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