引入
c++模板中,我们无法知道参数类是否具有某个成员,例如下面代码,我们希望下面的代码中能够打印t的成员变量a的值,然而当类型T不包含成员a时,调用下面的代码就会报错。
template<typename T>
static inline void print_if_has_a(const T& t){
printf("%d\n", t.a);
}
在c++中,我们能够通过SFINAE解决该问题,SFINAE全称为替换失败不是错误(Substitution Failure is not an Error),这里的替换指的是将参数类T替换为实际的类,不会报错则是指在函数重载的时候发生的替换失败不会报错(假如编译器能够发现更匹配的函数)。上面的例子使用SFINAE实现如下:
static inline void print_if_has_a(...) {}
template <typename T, typename = decltype(std::declval<T>().a)>
static inline void print_if_has_a(const T &t) {
printf("%d\n", t.a);
}
上述例子中 decltype(std::declval<T>().a)
实际是判断使用类型T的实例调用成员a的语句是否成立std::declval<T>()
用于生成一个类型T的实例。
在运行print_if_has_a(t)
时,若t不包含成员a,则 decltype(std::declval<T>().a)
无法推断出实际类型时,则替换失败,则不能使用第二个函数进行重载,故实际会调用第一个空函数。
通过SFINAE实现的效果就是,当print_if_has_a(t)
输入的t不包含成员变量a时,该语句就会在编译时被优化掉,不会产生多余的开销。
目的
本文的目的是实现一个简单通用的宏函数来实现对类成员的静态检查运行程序员分情况处理。
该宏函数的实现实际也是利用的SFINAE机制,好处就是不需要手动编写重载函数。
其形式如下:
int main(){
A x;
HY_IF_TEST(x, x.a,{
printf("%d\n", x.a);
},{})
}
该宏函数提供4个参数,第一个参数表示要测试的实例,第二个参数表示测试的语句能否在函数内调用,第三个参数表示测试成功时运行的代码块,第四个参数表示测试失败时运行的代码块。
代码
头文件,代码参考了boost::hana库。
#ifndef TEST_CPP_SFINAE_HPP
#define TEST_CPP_SFINAE_HPP
#include <functional>
namespace hy {
/**is valid referred from (boost::hana)**/
namespace type_detail {
template <typename F, typename... Args,
typename = decltype(std::declval<F &&>()(std::declval<Args &&>()...))>
constexpr auto is_valid_impl(int) {
return std::true_type{};
}
template <typename F, typename... Args> constexpr auto is_valid_impl(...) {
return std::false_type{};
}
template <typename F> struct is_valid_fun {
template <typename... Args> constexpr auto operator()(Args &&...) const {
return is_valid_impl<F, Args &&...>(int{});
}
};
} // namespace type_detail
struct is_valid_t {
template <typename F> constexpr auto operator()(F &&) const {
return type_detail::is_valid_fun<F &&>{};
}
template <typename F, typename... Args>
constexpr auto operator()(F &&, Args &&...) const {
return type_detail::is_valid_impl<F &&, Args &&...>(int{});
}
};
constexpr is_valid_t is_valid{};
/**if_**/
struct if_t {
template <typename Cond, typename Then, typename Else>
constexpr typename std::enable_if<std::is_same<Cond, std::true_type>::value,
Then &&>::type
operator()(Cond &&cond_, Then &&then_, Else &&else_) const {
return static_cast<Then &&>(then_);
}
template <typename Cond, typename Then, typename Else>
constexpr typename std::enable_if<std::is_same<Cond, std::false_type>::value,
Else &&>::type
operator()(Cond &&cond_, Then &&then_, Else &&else_) const {
return static_cast<Else &&>(else_);
}
};
constexpr if_t if_{};
#define HY_IF_TEST_(val, test_val, test_statement, then_code, else_code) \
hy::if_( \
hy::is_valid([&](auto &(test_val)) -> decltype(test_statement) {})(val), \
[&](auto &(test_val)) { then_code }, \
[&](auto &(test_val)) { else_code })(val);
#define HY_IF_TEST(val, test_statement, then_code, else_code) \
HY_IF_TEST_(val, val, test_statement, then_code, else_code)
} // namespace hy
#endif // TEST_CPP_SFINAE_HPP
测试
class A {
public:
int a = 0;
int func(int x) const { return x; }
};
int main(){
int i = 1;
HY_IF_TEST(i, i.a, { printf("int: %d\n", i.a); },
{
})
A a;
HY_IF_TEST(a, a.a, { printf("A: %d\n", a.a); },
{
})
}
程序只输出第二个代码块
观察汇编代码可以看到,编译好的程序只有一个打印指令,由于int类型不包含成员a,因此对其的打印处理被编译器直接优化掉了。
TIPS
该宏函数同样适用于不同类型的成员方法的测试,例如下面这样。
HY_IF_TEST(a, a.func(std::declval<int>()),
{ printf("func: %d\n", a.func(i)); },{})
这里注意如果测试需要输入参数的话,可以使用std::declval<T>()
来生成测试用的实例,对于基本类型直接使用右值常量也是ok的,例如下面这样:
HY_IF_TEST(a, a.func(0),
{ printf("func: %d\n", a.func(i)); },{})