在C++11通过SFINAE机制实现静态检查类成员是否存在并分情况处理,以及一种通用宏的实现

2023-11-17

引入

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)); },{})
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在C++11通过SFINAE机制实现静态检查类成员是否存在并分情况处理,以及一种通用宏的实现 的相关文章

  • 模板类包装任意类型/非类型模板类

    假设我有一个模板类base和一个班级wrapper其中包含一个实例化成员base 我想定义班级wrapper这样它依赖于模板参数包 该参数包只是 传递 给实例化成员base 例如 考虑下面的代码 它工作得很好 include
  • 将 2D 数组映射到 1D 数组

    我想用一维数组来表示一个二维数组 函数将传递两个索引 x y 和要存储的值 这两个索引代表一维数组的单个元素 并相应地设置它 我知道一维数组需要具有 arrayWidth arrayHeight 的大小 但我不知道如何设置每个元素 例如 如
  • 处理 LINQ sum 表达式中的 null

    我正在使用 LINQ 查询来查找列的总和 并且在少数情况下该值有可能为空 我现在使用的查询是 int score dbContext domainmaps Where p gt p SchoolId schoolid Sum v gt v
  • 实体框架代码优先 - 在另一个文件中配置

    使用 Fluent API 将表到实体的映射分开的最佳方法是什么 以便它全部位于单独的类中 而不是内联在 OnModelCreating 方法中 我目前在做什么 public class FooContext DbContext prote
  • .NET 可移植类库中的 .ToShortDateString 发生了什么

    我想知道为什么没有 ToShortDateString在 NET 可移植类库中 我有 2 个项目 Silverlight 和常规 NET 类库 使用相同的代码 并且代码涉及调用 ToShortDateString on a DateTime
  • 浮点提升:stroustrup vs 编译器 - 谁是对的?

    在 Stroustrup 的新书 C 编程语言 第四版 第 10 5 1 节中 他说 在执行算术运算之前 整数提升用于从较短的整数类型创建整数 类似地 浮点提升是用于从浮点数创建双精度数 我用以下代码确认了第一个声明 include
  • 对数字进行向上和向下舍入 C++

    我试图让我的程序分别向上和向下舍入数字 例如 如果数字是3 6 我的程序应该四舍五入最接近的数字 4 如果该数字是3 4 它将向下舍入为 3 我尝试使用ceil库获取 3 个项目的平均值 results ceil marks1 marks2
  • Qt中正确的线程方式

    我的图像加载非常耗时 图像很大 并且在加载时也完成了一些操作 我不想阻止应用程序 GUI 我的想法是在另一个线程中加载图像 发出图像已加载的信号 然后用该图像重绘视图 我的做法 void Window loadImage ImageLoad
  • 如何避免选择项目时 winforms 树视图图标发生变化

    我正在一个小型 C Winforms 应用程序中尝试树视图 我已经以编程方式将 ImageList 分配给树视图 并且所有节点都很好地显示了它们的图标 but当我单击一个节点时 它的图标会发生变化 变为 ImageList 中的第一个图像
  • 成员初始值设定项列表中的求值顺序是什么?

    我有一个带有一些参数的构造函数 我假设它们是按照列出的顺序初始化的 但在一种情况下 它们似乎是按相反的顺序初始化的 导致中止 当我反转参数时 程序停止中止 下面是我正在使用的语法的示例 a 之前需要初始化b 在这种情况下 你能保证这个初始化
  • 如何检测斑点并将其裁剪成 png 文件?

    我一直在开发一个网络应用程序 我陷入了一个有问题的问题 我会尝试解释我想要做什么 在这里您看到第一个大图像 其中有绿色形状 我想要做的是将这些形状裁剪成不同的 png 文件 并使它们的背景透明 就像大图像下面的示例裁剪图像一样 第一张图像将
  • for 循环 - 没有效果的语句

    由于某种原因 我收到错误 statement with no effect关于这个声明 for j idx j lt iter j increment printf from loop idx i int idx punc ctxt j 你
  • 在 C# 中赋值后如何保留有关对象的信息?

    我一直在问我的想法可能是解决方案 https stackoverflow com questions 35254467 is it possible in c sharp to get the attributes attached to
  • 在可观察项目生成时对其进行处理

    我有一个IObservable它会生成一次性物品 并且在其生命周期内可能会生成无限数量的物品 因此 我想在每次生成新项目时处理最后一个项目 因此Using http reactivex io documentation operators
  • C# ToString("MM/dd/yy") 删除前导 0 [重复]

    这个问题在这里已经有答案了 可能的重复 格式化 NET DateTime Day 不带前导零 https stackoverflow com questions 988353 format net datetime day with no
  • 通过 MSBuild 调用 cl.exe 时无限期挂起

    我正在尝试在我的 主要是 C 项目上运行 MSBuild 想象一下一个非常庞大的代码库 Visual Studio 2015 是有问题的工具集 Windows 7 SP1 和 VS 2015 更新 2 即使使用 m 1 从而迫使它仅使用一个
  • 为什么我不能在扩展 List 的类中调用 OrderBy?

    我有一堂课 Deck 其中包含一个名为的方法Shuffle 我正在致力于重构Deck延长List
  • 宏观评价[重复]

    这个问题在这里已经有答案了 可能的重复 未定义的行为和序列点 https stackoverflow com questions 4176328 undefined behavior and sequence points 我无法理解以下宏
  • Windows 上 libcurl 的静态库[重复]

    这个问题在这里已经有答案了 如何将此库 libcurl 静态链接到 exe 我努力了 disable share enable static 没有帮助 我使用的是MingW32 有没有一种简单的方法来静态链接这个库 这样我的应用程序就不再有
  • 最后从同一类中的其他构造函数调用构造函数

    我在这里读到可以调用另一个构造函数从同一类中的另一个构造函数调用构造函数 https stackoverflow com questions 829870 calling constructor from other constructor

随机推荐