元编程

好久没更新,不是没学习,是不想写到CSDN而已,都写在本地了。这次是因为我觉得我对于元编程的理解还可以了,使用上还是比较少,那就得练了。

文章目录

  • [1. 类型推导工具](#1. 类型推导工具)
  • [2. type traits](#2. type traits)
    • [2.1 基本例子](#2.1 基本例子)
    • [2.2 其他traits](#2.2 其他traits)
  • [3. SFINAE](#3. SFINAE)
    • [3.1 void_t](#3.1 void_t)
    • [3.2 SFINAE](#3.2 SFINAE)
    • [3.3 enable_if](#3.3 enable_if)

1. 类型推导工具

类型和值的转换

c++ 复制代码
template<class T,T v>
struct integral_constant{
    static const T value = v;
    typedef T value_type;
    typedef integral_constant type;
};

T是类型,V是值。

常用的就是bool_constant,值和类型对应起来了。

c++ 复制代码
template<bool,B>
using bool_constant = integral_constant<bool, B>;

typedef bool_constant<true>true_type;//<true_type能够拿到值
typedef bool_constant<false>false_type;

用途呢?值是不能用来重载的,但是类型是可以的。很多时候编译期拿到的都是值,比如is_same_v,用它可以转换成类型。

2. type traits

2.1 基本例子

判断:is_array,is_enum,is_function,is_reference,is_const,has_virtual_destructor等等

修改:decay,make_signed,remove_const,remove_cv,remove_reference等等

判断的例子:is_reference这个可以自己实现,有的实现不了。

c++ 复制代码
template<class T>
struct is_reference:public false_type{};

template<class T>
struct is_reference<T&>:public true_type{};

template<class T>
struct is_refernce<T&&>:public true_type{};

template<class T>
inline constexpr bool is_reference_v = is_reference<T>::value;

修改的例子:remove_const

c++ 复制代码
template<class T>
struct remove_const{
    typedef T type;
};

template<class T>
struct remove_const<const T>{
    typedef T type;
};

template<class T>
using remove_const_t = typename remove_const<T>::type

有个坑,const string &,或者const int &这种结构,remove_const后结果都还是自己,因为他们最外层是ref,即便用is_const得到的都是假,要像洋葱一样一层一层剥,最外层是referencce,再一层才是const。如果需要得到string &,需要先去掉reference,再去掉const,再加上reference。这个的主要原因是const string&应该写成string const&,我们习惯反着写。

gpt给的例子

c++ 复制代码
#include <bits/stdc++.h>

#include <iostream>
#include <type_traits>
template <typename T>
void remove_const_example() {
  std::cout << std::is_reference<T>::value << std::endl;
  std::cout << std::is_const<T>::value << " -> "  // 输出是否是 const 类型
            << std::is_const<typename std::remove_const<T>::type>::value
            << std::endl;  // 输出移除 const 后的类型是否是 const
}

int main() {
  remove_const_example<const int&>();
  // remove_const_example<const int>();  // 输出: 1 -> 0
  // remove_const_example<int>();        // 输出: 0 -> 0
  // remove_const_example<const double>(); // 输出: 1 -> 0
  return 0;
}

2.2 其他traits

吴老师举了几个他常用的例子

is_pod的traits不建议用了,最新的c++已经没有了,因为这一般不是我们想要的,用下面两个替换掉了

  • is_standard_layout:内存使用标准布局,其实就是没有虚函数、没有继承、没有static函数这些。

  • is_trivial:默认构造、复制、析构是不是不需要做任何特殊处理。分为下面两种

    • is_trivially_default_constructible:默认构造不需要执行任何动作
    • is_trivially_copyable:复制时可以简单复制内存块,不需要做特殊处理。这个还分为下面四种
      • is_trivially_copy_constructible:拷贝构造可以简单复制内存块
      • is_trivially_copy_assignable:拷贝赋值可以复制简单内存块
      • is_trivially_move_constructible:移动构造可以复制简单内存块
      • is_trivially_mvoe_assignable:移动赋值可以复制简单内存块
      • is_trivially_destructible:析构不需要执行任何操作

3. SFINAE

3.1 void_t

如果变参模板中能够正常匹配就去掉全部类型,只剩void,如果不能,就直接失败(SFINAE)。

c++ 复制代码
template<typename...>
using void_t=void;

3.2 SFINAE

用void_t实现has_reserve

c++ 复制代码
#include <bits/stdc++.h>

template <typename T, typename C= void>
struct has_reserve : std::false_type {
  has_reserve() { std::cout << "not" << std::endl; }
};

template <typename T>
struct has_reserve<T, std::void_t<decltype(std::declval<T&>().reserve(1U))>>
    : std::true_type {
  has_reserve() { std::cout << "has" << std::endl; }
};
struct A {};
struct B {
  void reserve(unsigned int) {}
};
int main() {
  has_reserve<A> a;
  has_reserve<B> b;
}

我之前为什么一直没理解SFINAE,其实主要是没理解默认参数的特化。有几个核心点

  1. 为什么主模板要写个默认参数?
  • 先说答案,不写默认的,就写个typename = void也是可以的(其实这个更好),原因在下面。
  • 这个默认参数也不是必须是void,但是为了要用下面的std::void_t就只能用void,我尝试写了一个int_t,也是可以的。
  • 特化版本的第二个如果能够满足里面的条件,就变成了has_reserve<T,void>。前面说到void是默认参数,或者说正常情况是没人给has_reserve传第二个参数的,也就是说第二个参数必须是void。而特化版本第二个也必须是void才能匹配上主模板。还是那句话,主模板是void不是因为自己,是因为特化需要void_t。
  1. 如果主模板的默认参数是int,其它不变会怎么样?

特化会依然是它的特化,但是不会被匹配到。还是那句话,没人会传入第二个参数,就是说,你写的 has_reserve<A> a;会变成 has_reserve<A,int> a;,怎么可能和 has_reserve<T,void>匹配呢?

如果你非要说,我就这么写has_reserve<A,void> a;,那好,你能匹配上。

  1. 特化std::void_t里面是什么呢?

这里面确实有点乱,但还不至于恶心到看不懂。std::declval<T&>()用于在编译期构造一个对象,这个东西我搜来搜去基本都用来检查是否有某个函数了。cppreference上说哪怕它的构造不能调,也可以这么写,因为只有编译阶段生效。注意declval前面一定要加std。

3.3 enable_if

先说enable_if的实现。第一个是参数是true,type就返回T,不是,就不满足sfinae标准,匹配失败。

c++ 复制代码
template<bool B, class T = void>
struct enable_if {};
 
template<class T>
struct enable_if<true, T> { typedef T type; };

上面的扩展

c++ 复制代码
#include <bits/stdc++.h>

template <typename T, typename = void>
struct has_reserve : std::false_type {
  has_reserve() { std::cout << "not" << std::endl; }
};

template <typename T>
struct has_reserve<T, std::void_t<decltype(std::declval<T&>().reserve(1U))>>
    : std::true_type {
  has_reserve() { std::cout << "has" << std::endl; }
};

template <typename C>
std::enable_if_t<has_reserve<C>::value, void> put_data(C& container) {
  std::cout << "has func" << std::endl;
}

template <typename C>
std::enable_if_t<!has_reserve<C>::value, void> put_data(C& container) {
  std::cout << "not" << std::endl;
}
struct A {};
struct B {
  void reserve(unsigned int) {}
};
int main() {
  has_reserve<A> a;
  has_reserve<B, void> b;
  std::vector<int> c;
  std::list<int> d;
  put_data(c);
  put_data(d);
}

首先,我把主模板的默认参数改掉了。这种写法相当于把enable加到了返回值上。回到enable_if的定义,如果第一个value失败了就SFINAE失败,换下一个,如果是真的就直接返回第二个参数:void。如果函数需要有返回值那就把enable_if后面的void换成其它类型,比如int。

相关推荐
高山我梦口香糖29 分钟前
[react]searchParams转普通对象
开发语言·前端·javascript
冷眼看人间恩怨42 分钟前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
信号处理学渣1 小时前
matlab画图,选择性显示legend标签
开发语言·matlab
红龙创客1 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin1 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
jasmine s1 小时前
Pandas
开发语言·python
biomooc1 小时前
R 语言 | 绘图的文字格式(绘制上标、下标、斜体、文字标注等)
开发语言·r语言
骇客野人1 小时前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
black^sugar1 小时前
纯前端实现更新检测
开发语言·前端·javascript
404NooFound2 小时前
Python轻量级NoSQL数据库TinyDB
开发语言·python·nosql