将 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>){}
};
这样我们就可以更明确地了解 a
和 b
的预期值是什么。这很有效,尤其是因为您的条件都是 &&
。肉眼也更明显,没有两个重载是相同的。
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
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>){}
};
这样我们就可以更明确地了解 a
和 b
的预期值是什么。这很有效,尤其是因为您的条件都是 &&
。肉眼也更明显,没有两个重载是相同的。
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)
{/*..*/}