跟我学c++高级篇——模板元编程之四Wrapper
一、Wrapper
c++ Wrapper可以理解为外覆器或者包装器。它既是一种设计方法,又是一种元编程应用的技巧,这个在c++STL应用非常广泛,可以处理类型、控制分支、CV的控制等等。在标准库里有有基础的std::integral_constant,还有其它的如引用外覆器等。下面会分别对其进行介绍。
为什么需要外覆器?它其是有以下几个作用:
1、类型的选择,元编程在编译期确定具体的类型
2、逻辑控制,元编程在编译期处理,无法使用if else
3、Lazy evaluation,延迟加载或叫延迟计算
二、常见的包装器
1、整形包装器
先看一下STL中的整形外覆器的实现:
template<class T, T v>
struct integral_constant {
static constexpr T value = v;
using value_type = T;
using type = integral_constant; // using injected-class-name
constexpr operator value_type() const noexcept { return value; }
constexpr value_type operator()() const noexcept { return value; } // since c++14
};
STL中的整形类型包装器和真实Boost库的略微有些差别,可能出于功能最小原则把一些迭代功能删除了。同时把一些操作运算符如equal等挪到了算法库里。
提到整形外覆器就必须得提到bool型的别名模板:std::bool_constant以及两个特化:true_type和false_type。有兴趣可以看看他们的实现及应用,都很简单,正好实现了开头提到的功能。
2、STL中的相关外覆器
在标准库里有很多类似的外覆器,看下面的例子:
引用外覆器,从std::reference_wrapper
namespace detail {
template <class T> T& FUN(T& t) noexcept { return t; }
template <class T> void FUN(T&&) = delete;
}
template <class T>
class reference_wrapper {
public:
// 类型
using type = T;
// 构造/复制/销毁
template <class U, class = decltype(
detail::FUN<T>(std::declval<U>()),
std::enable_if_t<!std::is_same_v<reference_wrapper, std::remove_cvref_t<U>>>()
)>
constexpr reference_wrapper(U&& u) noexcept(noexcept(detail::FUN<T>(std::forward<U>(u))))
: _ ptr(std::addressof(detail::FUN<T>(std::forward<U>(u)))) {}
reference_wrapper(const reference_wrapper&) noexcept = default;
// 赋值
reference_wrapper & operator=(const reference_wrapper& x) noexcept = default;
// 访问
constexpr operator T& () const noexcept { return * _ ptr; }
constexpr T& get() const noexcept { return * _ ptr; }
template< class... ArgTypes >
constexpr std::invoke_result_t<T&, ArgTypes...>
operator() ( ArgTypes&&... args ) const {
return std::invoke(get(), std::forward<ArgTypes>(args)...);
}
private:
T* _ptr;
};
// 推导指引
template<class T>
reference_wrapper(T&) -> reference_wrapper<T>;
从这个可以推导出std::ref, std::cref,这两个写过std::thread的应该熟悉。另外还有拷贝外覆器(Copyable wrapper-C++20),std::type_index等等。
三、例程
外覆器的例程有很多,看一下cppreference上的例程:
#include <algorithm>
#include <list>
#include <vector>
#include <iostream>
#include <numeric>
#include <random>
#include <functional>
int main()
{
std::list<int> l(10);
std::iota(l.begin(), l.end(), -4);
std::vector<std::reference_wrapper<int>> v(l.begin(), l.end());
// 不能在 list 上用 shuffle (要求随机访问),但能在 vector 上使用它
std::shuffle(v.begin(), v.end(), std::mt19937{std::random_device{}()});
std::cout << "Contents of the list: ";
for (int n : l) std::cout << n << ' '; std::cout << '\n';
std::cout << "Contents of the list, as seen through a shuffled vector: ";
for (int i : v) std::cout << i << ' '; std::cout << '\n';
std::cout << "Doubling the values in the initial list...\n";
for (int& i : l) {
i * = 2;
}
std::cout << "Contents of the list, as seen through a shuffled vector: ";
for (int i : v) std::cout << i << ' '; std::cout << '\n';
}
上面是引用外覆器的,下面再看一个std::type_index的:
#include <iostream>
#include <typeinfo>
#include <typeindex>
#include <unordered_map>
#include <string>
#include <memory>
struct A {
virtual ~A() {}
};
struct B : A {};
struct C : A {};
int main()
{
std::unordered_map<std::type_index, std::string> type_names;
type_names[std::type_index(typeid(int))] = "int";
type_names[std::type_index(typeid(double))] = "double";
type_names[std::type_index(typeid(A))] = "A";
type_names[std::type_index(typeid(B))] = "B";
type_names[std::type_index(typeid(C))] = "C";
int i;
double d;
A a;
// 注意我们正在存储指向类型 A 的指针
std::unique_ptr<A> b(new B);
std::unique_ptr<A> c(new C);
std::cout << "i is " << type_names[std::type_index(typeid(i))] << '\n';
std::cout << "d is " << type_names[std::type_index(typeid(d))] << '\n';
std::cout << "a is " << type_names[std::type_index(typeid(a))] << '\n';
std::cout << "b is " << type_names[std::type_index(typeid(*b))] << '\n';
std::cout << "c is " << type_names[std::type_index(typeid(*c))] << '\n';
}
基础的东西单纯拿出来看,相当枯燥,但不理解这些,看一些组合代码时,就会晕菜。
四、总结
Wrapper不管如何翻译,只要明白了它的含义就好,其实有些东西本来就很难达到中英完全相通的地步。Wrapper是一种实现静态多态的基础手段,通过Wrapper,可以在元编程中有效控制类型和逻辑运算。包装器是一项非常基础的构建工具,把它弄清楚,掌握好,才能更好的进行相关元编程开发。特别对STL中的一些基础的包装器的意义理解到位,才能把STL中的元编程封装真正用到合适的编程场景中。
坎井之蛙,也有外探宇宙之心!