C++ 模板元编程 笔记
很有意思但不知道实际有啥用的东西
链表
#include <iostream>
#include <type_traits>
/*
功能:
- 获取 size
- 将 List 从指定位置拆分成两个
- 合并两个 List
- 读取/删除某位置的元素
- 在头部/末尾/任意位置插入元素
- 查询某元素第一次出现的位置
(修改指定位置的值,可以通过删除+插入实现,懒得再封装了)
可以支持任意类型而不是 int,再加一个模板参数 T 即可。
*/
template <int... values>
struct List;
template <>
struct List<> {
static constexpr size_t size = 0;
};
using EmptyList = List<>;
template <int head, int... values>
struct List<head, values...> {
static constexpr int front = head;
static constexpr size_t size = 1 + sizeof...(values);
using PopFrontT = std::conditional_t<size == 1, EmptyList, List<values...>>;
};
// template <int head>
// struct List<head> { // 长度为 1 的情况
// static constexpr int front = head;
// static constexpr size_t size = 1;
// using PopFrontT = EmptyList;
// };
// --- insert
template <typename T, int value>
struct PushFront;
template <int... values, int x>
struct PushFront<List<values...>, x> {
using type = List<x, values...>;
};
template <typename T, int value>
struct PushBack;
template <int... values, int x>
struct PushBack<List<values...>, x> {
using type = List<values..., x>;
};
// --- split:将 List 从指定位置拆分成两个
template <size_t pos, typename L, typename R>
struct SplitImpl;
template <size_t now, int... L, int... R>
struct SplitImpl<now, List<L...>, List<R...>> { // 每次取出 R 中的一个放入 L
using next = SplitImpl<now - 1, List<L..., List<R...>::front>, typename List<R...>::PopFrontT>;
using newL = typename next::newL;
using newR = typename next::newR;
};
template <int... L, int... R>
struct SplitImpl<0, List<L...>, List<R...>> {
using newL = List<L...>;
using newR = List<R...>;
};
template <size_t pos, typename T>
struct Split;
template <size_t pos, int... values>
struct Split<pos, List<values...>> {
using L = typename SplitImpl<pos, EmptyList, List<values...>>::newL;
using R = typename SplitImpl<pos, EmptyList, List<values...>>::newR;
};
// --- merge:合并两个 List
template <typename L, typename R>
struct Merge;
template <int... L, int... R>
struct Merge<List<L...>, List<R...>> {
using type = List<L..., R...>;
};
// --- get:读取某位置的元素
template <size_t pos, typename T>
struct Get;
template <size_t pos, int... values>
struct Get<pos, List<values...>> {
static constexpr int value = Get<pos - 1, typename List<values...>::PopFrontT>::value;
};
template <int... values>
struct Get<0, List<values...>> {
static constexpr int value = List<values...>::front;
};
// --- delete:删除某位置的元素
template <size_t pos, typename T>
struct Delete;
template <size_t pos, int... values>
struct Delete<pos, List<values...>> {
using temp = Split<pos, List<values...>>;
using type = typename Merge<typename temp::L, typename temp::R::PopFrontT>::type;
};
// --- insert:在某位置插入元素
template <size_t pos, int x, typename T>
struct Insert;
template <size_t pos, int x, int... values>
struct Insert<pos, x, List<values...>> {
using temp = Split<pos, List<values...>>;
using type = typename Merge<typename PushBack<typename temp::L, x>::type, typename temp::R>::type;
};
// --- find:查询某元素第一次出现的位置
template <typename T, int v, int now>
struct FindImpl;
template <int v, int front, int... values, int now>
struct FindImpl<List<front, values...>, v, now> {
static constexpr int pos = v == front ? now : FindImpl<List<values...>, v, now + 1>::pos;
};
template <int v, int now>
struct FindImpl<List<>, v, now> {
static constexpr int pos = -1;
};
template <typename T, auto value>
struct Find;
template <int v, int... values>
struct Find<List<values...>, v>: FindImpl<List<values...>, v, 0> {};
// ---
int main() {
using L1 = List<1, 2, 3, 4>;
using L1l = Split<2, L1>::L;
using L2 = List<3, 4, 5>;
using L1l2 = Merge<L1l, L2>::type;
using L2pf = PushFront<L2, 2>::type;
using L2pfpb = PushBack<L2pf, 6>::type;
using L2d1 = Delete<0, L2pfpb>::type;
using L2d2 = Delete<2, L2pfpb>::type;
using L2d3 = Delete<4, L2pfpb>::type;
using L3i1 = Insert<0, 0, L1>::type;
using L3i2 = Insert<2, 0, L1>::type;
using L3i3 = Insert<4, 0, L1>::type;
[](...) {} (
EmptyList::size,
L1::size,
Get<0, L1>::value,
Get<3, L1>::value,
// Get<4, L1>::value, // 可以在编译期检查越界(因为没有该模板的定义)
Find<L1, 1>::pos,
Find<L1, 3>::pos,
Find<L1, 4>::pos,
Find<L1, 5>::pos
);
}
数组
#include <cstdio>
#include <vector>
#include <iostream>
#include <type_traits>
/*
功能:
- 获取 size
- 拆分一个数组
- 合并两个数组
- 读写某位置的元素
- 在任意位置插入元素
实现与链表类似,只是有数组后可直接获取某位置的元素。
支持任意类型(只能有一种,也可以用 auto 和 common_type 实现而不是参数 T)。
template<auto... items>
struct Array {
using type = std::common_type_t<decltype(items)...>;
static constexpr type value[sizeof...(items)] = {items...};
};
*/
template<typename T, T... values>
struct Array;
template<typename T, T head, T... values>
struct Array<T, head, values...> {
using ValueType = T;
static constexpr size_t size = 1 + sizeof...(values);
static constexpr T front = head;
static constexpr T data[] = {head, values...};
using PopFrontT = Array<T, values...>;
static_assert(size > 0);
};
template<typename T>
struct Array<T> {
using ValueType = T;
static constexpr size_t size = 0;
};
template<typename T>
using EmptyArray = Array<T>;
// split
template <size_t pos, typename L, typename R>
struct SplitImpl;
template <size_t now, typename T, T... L, T... R>
struct SplitImpl<now, Array<T, L...>, Array<T, R...>> {
using next = SplitImpl<now - 1, Array<T, L..., Array<T, R...>::front>, typename Array<T, R...>::PopFrontT>;
using newL = typename next::newL;
using newR = typename next::newR;
};
template <typename T, T... L, T... R>
struct SplitImpl<0, Array<T, L...>, Array<T, R...>> {
using newL = Array<T, L...>;
using newR = Array<T, R...>;
};
template <size_t pos, typename T>
struct Split;
template <size_t pos, typename T, int... values>
struct Split<pos, Array<T, values...>> {
using L = typename SplitImpl<pos, EmptyArray<T>, Array<T, values...>>::newL;
using R = typename SplitImpl<pos, EmptyArray<T>, Array<T, values...>>::newR;
};
// merge:合并两个 Array
template <typename L, typename R>
struct Merge;
template <typename T, T... L, T... R>
struct Merge<Array<T, L...>, Array<T, R...>> {
using type = Array<T, L..., R...>;
};
// get:读取某位置的元素
template <size_t pos, typename T>
struct Get;
template <size_t pos, typename T, T... values>
struct Get<pos, Array<T, values...>> {
static constexpr T value = Array<T, values...>::data[pos];
};
// 其它同链表
int main() {
using A1 = Array<int, 1, 2, 3, 4>;
using A1l = Split<2, A1>::L;
using A2 = Array<int, 3, 4, 5>;
using A1l2 = Merge<A1l, A2>::type;
[](...) {} (
EmptyArray<int>::size,
A1::size,
Get<0, A1>::value,
Get<3, A1>::value
// Get<4, L1>::value // 可以在编译期检查越界(因为没有该模板的定义)
);
}
归并排序
#include <iostream>
#include <type_traits>
template <int... values>
struct List;
template <>
struct List<> {
static constexpr size_t size = 0;
};
using EmptyList = List<>;
template <int head, int... values>
struct List<head, values...> {
static constexpr int front = head;
static constexpr size_t size = 1 + sizeof...(values);
using PopFrontT = std::conditional_t<size == 1, EmptyList, List<values...>>;
};
// --- insert
template <typename T, int value>
struct PushFront;
template <int... values, int x>
struct PushFront<List<values...>, x> {
using type = List<x, values...>;
};
// --- split:将 List 从指定位置拆分成两个
template <size_t pos, typename L, typename R>
struct SplitImpl;
template <size_t now, int... L, int... R>
struct SplitImpl<now, List<L...>, List<R...>> { // 每次取出 R 中的一个放入 L
using next = SplitImpl<now - 1, List<L..., List<R...>::front>, typename List<R...>::PopFrontT>;
using newL = typename next::newL;
using newR = typename next::newR;
};
template <int... L, int... R>
struct SplitImpl<0, List<L...>, List<R...>> {
using newL = List<L...>;
using newR = List<R...>;
};
template <size_t pos, typename T>
struct Split;
template <size_t pos, int... values>
struct Split<pos, List<values...>> {
using L = typename SplitImpl<pos, EmptyList, List<values...>>::newL;
using R = typename SplitImpl<pos, EmptyList, List<values...>>::newR;
};
// --- merge:合并两个 List
// template <typename L, typename R>
// struct Merge;
// template <int... L, int... R>
// struct Merge<List<L...>, List<R...>> {
// using type = List<L..., R...>;
// };
// --- merge:合并两个有序链表
template <typename L, typename R>
struct Merge;
template <int... L, int... R>
struct Merge<List<L...>, List<R...>> {
using l = List<L...>;
using r = List<R...>;
static constexpr int min = l::front < r::front ? l::front : r::front;
using temp = std::conditional_t<
l::front < r::front,
typename Merge<typename l::PopFrontT, r>::type,
typename Merge<l, typename r::PopFrontT>::type
>;
using type = typename PushFront<temp, min>::type; // 每次取出较小值,然后递归处理后面的序列
};
// 递归边界
template <int... R>
struct Merge<EmptyList, List<R...>> {
using type = List<R...>;
};
template <int... L>
struct Merge<List<L...>, EmptyList> {
using type = List<L...>;
};
// --- mergeSort:归并排序:每次将链表拆分,递归排序,然后合并
template <typename L>
struct MergeSort;
template <int... values>
struct MergeSort<List<values...>> {
using list = List<values...>;
using l = Split<list::size / 2, list>::L;
using r = Split<list::size / 2, list>::R;
using sl = MergeSort<l>::type;
using sr = MergeSort<r>::type;
using type = Merge<sl, sr>::type;
};
// 模板递归需要定义边界!否则 MergeSort<List<values...>> 定义完之前无法递归使用 MergeSort<List<>> 和 MergeSort<List<v>>
template <int v>
struct MergeSort<List<v>> {
using type = List<v>;
};
template <>
struct MergeSort<List<>> {
using type = EmptyList;
};
// ---
int main() {
using L1 = List<1, 4, 2, 3>;
using L1l = Split<2, L1>::L;
using L2 = List<2, 3, 4, 5>;
using L1l2 = Merge<L1l, L2>::type;
using L2pf = PushFront<L2, 2>::type;
using S1 = Split<0, EmptyList>::L;
using S2 = Split<0, EmptyList>::R;
using R1 = MergeSort<EmptyList>::type;
using R2 = MergeSort<List<3>>::type;
using L3 = List<5, 2, 1, 9, 4, 2, 3, 0, 6>;
using L3r = MergeSort<L3>::type;
[](...) {} (
EmptyList::size,
L1::size,
L3::size,
L3r::size
);
}
std::variant
#include <vector>
#include <cstring>
#include <cassert>
#include <variant>
#include <iostream>
#include <type_traits>
using std::cout;
/*
[variant](https://zh.cppreference.com/w/cpp/utility/variant)
- 切换类型时析构旧对象、创建新对象。
- 接口:get、emplace、swap。错误时抛出异常。
- 辅助类/函数:get、get_if、holds_alternative、variant_alternative、visit。
- 同一类型可以出现多次,但此时只能用下标访问。
基本实现就是利用 char 数组作为存储,其大小为各类型大小的最大值。在每次切换类型时析构并重新创建对象。
需要维护当前的类型 type,来验证读取的有效性(不能读取非上次写入的类型)。因为不能将类型作为变量,所以 type 是 int 下标。
析构时,需要根据 type 确定当前类型 T,以便将 data 转为 T* 然后调用 ~T()。可以用将每种类型的 Destroy 函数保存在一个函数指针数组里,用下标调用。
注意,下标到类型的转换、类型到下标的转换,都是只能在编译期进行的。type 是运行期变量,所以无法用于这些转换,必须手动指定或根据参数值推导出 T 或下标。
注意:
- data 要对齐。
- reinterpret_cast 本身不会隐式创建对象。切换时需要使用 placement new 构造。
- 由于指针可互转换的要求,即使 data 位置有新的对象,直接 reinterpret_cast 转换 data 访问也是 UB?需要 launder 或使用 placement new 返回的指针。
- 使用 T 初始化时应用 decay(T) 匹配。否则 "abc" *只会*直接匹配 const char&[4],然后才尝试隐式转换到 string 与 const char*(且按顺序选择),无法匹配 const char[4]。但是不需要支持保存数组,只存指针即可。
- 构造时应使用 T(...) 而非 T{...},对于有初始化列表构造函数的类(如 string、vector)会导致不同的结果。
未完善:
- Variant 在多种异常情况下会变得无值,通过 valueless_by_exception 检测。会影响多种函数的执行。
- Variant<float, long, double> z = 0; 应匹配 long,且 int 始终不会匹配浮点、char、short。但不知道怎么实现哪个更匹配,所以目前隐式转换会匹配第一个合适的类型,即使有多个或更合适的。
*/
// --- 获取类型最大大小与对齐
template <typename T, typename... Ts>
struct MaxSize {
static constexpr size_t value = sizeof(T) >= MaxSize<Ts...>::value ? sizeof(T) : MaxSize<Ts...>::value;
};
template <typename T>
struct MaxSize<T> {
static constexpr size_t value = sizeof(T);
};
// 其实不需要,alignas(T...) 也可展开。对象会取最大的对齐值
template <typename T, typename... Ts>
struct MaxAlign {
static constexpr size_t value = alignof(T) >= MaxAlign<Ts...>::value ? alignof(T) : MaxAlign<Ts...>::value;
};
template <typename T>
struct MaxAlign<T> {
static constexpr size_t value = alignof(T);
};
// --- 下标到类型的转换
template <size_t pos, typename T, typename... Ts>
struct IdToType {
static_assert(pos < 1 + sizeof...(Ts));
using type = typename IdToType<pos - 1, Ts...>::type;
};
template <typename T, typename... Ts>
struct IdToType<0, T, Ts...> {
using type = T;
};
// --- 类型到下标的转换
template <size_t pos, typename X, typename T, typename... Ts>
struct TypeToIdImpl {
static constexpr size_t value = std::is_same_v<X, T> ? pos : TypeToIdImpl<pos + 1, X, Ts...>::value;
};
template <size_t pos, typename X, typename T>
struct TypeToIdImpl<pos, X, T> {
static constexpr size_t value = std::is_same_v<X, T> ? pos : (size_t)-1;
};
template <typename X, typename... Ts>
struct TypeToId {
static constexpr size_t value = TypeToIdImpl<0, X, Ts...>::value;
};
// --- 类型到下标的转换,但允许隐式转换,类型不必完全相同
template <size_t pos, typename X, typename T, typename... Ts>
struct ConvertTypeToIdImpl {
static constexpr size_t value = std::is_convertible_v<X, T> ? pos : ConvertTypeToIdImpl<pos + 1, X, Ts...>::value;
};
template <size_t pos, typename X, typename T>
struct ConvertTypeToIdImpl<pos, X, T> {
static constexpr size_t value = std::is_convertible_v<X, T> ? pos : (size_t)-1;
};
template <typename X, typename... Ts>
struct ConvertTypeToId {
static constexpr size_t value = ConvertTypeToIdImpl<0, X, Ts...>::value;
};
// --- utility
// 计算类型出现数量
template <typename X, typename T, typename... Ts>
struct CountType {
static constexpr size_t value = (std::is_same_v<X, T> ? 1 : 0) + CountType<X, Ts...>::value;
};
template <typename X, typename T>
struct CountType<X, T> {
static constexpr size_t value = (std::is_same_v<X, T> ? 1 : 0);
};
// 判断 V 是否是模板 T 的实例化
template <template<typename...> class T, typename V> // T 拥有模板参数 typename...
struct T_is_instantiation_of: std::false_type {};
template <template<typename...> class T, typename... Ts>
struct T_is_instantiation_of<T, T<Ts...>>: std::true_type {};
template <template<size_t...> class T, typename V> // T 拥有模板参数 size_t...
struct V_is_instantiation_of: std::false_type {};
template <template<size_t...> class T, size_t... Ts>
struct V_is_instantiation_of<T, T<Ts...>>: std::true_type {};
// --- [消歧义标签](https://zh.cppreference.com/w/cpp/utility/in_place)
// 构造 Variant 时,需传递 std::in_place_type<T> 和 T 的构造参数,指明要使用的类型是 T(也可指明下标)
template <class T>
struct in_place_type_t {
explicit in_place_type_t() = default;
};
template <class T>
inline constexpr in_place_type_t<T> in_place_type{};
template <std::size_t I>
struct in_place_index_t {
explicit in_place_index_t() = default;
};
template <std::size_t I>
inline constexpr in_place_index_t<I> in_place_index{};
// --- 其它
template <typename... Ts>
struct Variant;
template <class T>
static void DestroyImpl(unsigned char* data) {
if constexpr (!std::is_trivially_destructible_v<T>) {
std::launder(reinterpret_cast<T*>(data))->~T();
}
}
// --- variant_alternative:将下标转为类型(获取当前选项)。如果 variant 有 const 限定则传给类型。
template <size_t I, class T>
struct variant_alternative;
template <std::size_t I, class... Ts>
struct variant_alternative<I, Variant<Ts...>> {
static_assert(I < sizeof...(Ts));
using type = IdToType<I, Ts...>::type;
};
template <std::size_t I, class... Ts>
class variant_alternative<I, const Variant<Ts...>> {
static_assert(I < sizeof...(Ts));
using type = std::add_const_t<typename IdToType<I, Ts...>::type>;
};
template <size_t I, class T>
using variant_alternative_t = variant_alternative<I, T>::type;
template <class T, class... Ts>
constexpr bool holds_alternative(const Variant<Ts...>& v) noexcept {
try {
v.template get<T>();
} catch (const std::bad_variant_access&) {
return false;
}
return true;
}
// --- visit
template <typename F, typename V, size_t index>
static auto visitImpl(F&& func, V& var) {
return func(var.template get<index>());
}
template <typename F, typename V>
auto visit(F&& func, V&& var)
requires T_is_instantiation_of<Variant, std::decay_t<V>>::value
{
return var.visit(std::forward<F>(func));
}
// --- variant
static constexpr size_t variant_npos = -1;
template <typename... Ts>
struct Variant {
static_assert(sizeof...(Ts) > 0,
"variant must have at least one alternative");
static_assert(!(std::is_reference_v<Ts> || ...),
"variant must have no reference alternative");
static_assert(!(std::is_void_v<Ts> || ...),
"variant must have no void alternative");
using T0 = IdToType<0, Ts...>::type;
constexpr Variant()
noexcept(std::is_nothrow_default_constructible_v<T0>)
requires std::is_default_constructible_v<T0>
{
type = 0;
new (data) T0{};
}
/*
没有办法在运行时得知当前 Variant 保存的类型,所以无法实现这两个构造函数。
只有像下面的函数一样,将参数写为模板参数(编译期常量)才可以。
将 Ts... 和当前的 type_info::name 作为 const char* 保存,可以实时判断当前的 typeid,但是没法把它转换回类型。
*/
Variant(const Variant& other) = delete;
Variant(Variant&& other) = delete;
// constexpr Variant(const Variant& other)
// requires (std::is_copy_constructible_v<Ts> && ...)
// {
// type = other.type;
// using T = IdToType<type, Ts...>::type; // error:type 不是编译时常量,不能用于编译期的类型获取
// new (data) T(other.get<type>());
// }
// constexpr Variant(Variant&& other)
// requires (std::is_move_constructible_v<Ts> && ...)
// {
// type = other.type;
// using T = IdToType<type, Ts...>::type;
// new (data) T(std::move(other.get<type>()));
// }
template <class T>
constexpr Variant(T&& t)
requires (
!std::is_same_v<std::decay_t<T>, Variant> &&
!T_is_instantiation_of<in_place_type_t, T>::value &&
!V_is_instantiation_of<in_place_index_t, T>::value &&
(TypeToId<std::decay_t<T>, Ts...>::value != variant_npos || ConvertTypeToId<std::decay_t<T>, Ts...>::value != variant_npos)
) // 可以再限制一下类型 T 在 Ts 的出现次数
{
using DT = std::decay_t<T>;
constexpr size_t id = TypeToId<DT, Ts...>::value != variant_npos ? TypeToId<DT, Ts...>::value : ConvertTypeToId<DT, Ts...>::value;
using RT = IdToType<id, Ts...>::type;
static_assert(std::is_constructible_v<RT, T>);
type = id;
new (data) RT(std::forward<T>(t));
}
template <class T>
Variant& operator =(T&& t)
requires (
!std::is_same_v<std::decay_t<T>, Variant> &&
(TypeToId<std::decay_t<T>, Ts...>::value != variant_npos || ConvertTypeToId<std::decay_t<T>, Ts...>::value != variant_npos)
)
{
using DT = std::decay_t<T>;
constexpr size_t id = TypeToId<DT, Ts...>::value != variant_npos ? TypeToId<DT, Ts...>::value : ConvertTypeToId<DT, Ts...>::value;
using RT = IdToType<id, Ts...>::type;
static_assert(std::is_assignable_v<RT&, T>);
Destroy();
type = id;
new (data) RT(std::forward<T>(t));
return *this;
}
template <class T, class... Args>
constexpr explicit Variant(in_place_type_t<T>, Args&&... args)
requires (
std::is_constructible_v<T, Args...> &&
TypeToId<T, Ts...>::value != variant_npos &&
CountType<T, Ts...>::value == 1
)
{
type = TypeToId<T, Ts...>::value;
new (data) T(std::forward<Args>(args)...);
}
template <size_t I, class... Args>
constexpr explicit Variant(in_place_index_t<I>, Args&&... args)
requires (I < sizeof...(Ts)) && std::is_constructible_v<typename IdToType<I, Ts...>::type, Args...>
{
type = I;
using T = IdToType<I, Ts...>::type;
new (data) T(std::forward<Args>(args)...);
}
// emplace
template <class T, class... Args>
T& emplace(Args&&... args)
requires (
std::is_constructible_v<T, Args...> &&
CountType<T, Ts...>::value == 1
)
{
Destroy();
type = TypeToId<T, Ts...>::value;
return *(new (data) T(std::forward<Args>(args)...));
}
template <std::size_t I, class... Args>
variant_alternative_t<I, Variant>& emplace(Args&&... args)
requires (I < sizeof...(Ts)) && std::is_constructible_v<typename IdToType<I, Ts...>::type, Args...>
{
Destroy();
type = I;
using T = IdToType<I, Ts...>::type;
return *(new (data) T(std::forward<Args>(args)...));
}
void Destroy() {
if (type != variant_npos) {
destroyFuncs[type](data);
type = variant_npos;
}
}
// get
template <typename T>
T& get() {
static_assert(CountType<T, Ts...>::value == 1);
constexpr size_t index = TypeToId<T, Ts...>::value;
return get<index>();
}
template <typename T>
const T& get() const {
static_assert(CountType<T, Ts...>::value == 1);
constexpr size_t index = TypeToId<T, Ts...>::value;
return get<index>();
}
template <size_t index>
typename IdToType<index, Ts...>::type& get() {
if (index != type) {
throw std::bad_variant_access();
}
return *std::launder(reinterpret_cast<typename IdToType<index, Ts...>::type*>(data));
}
template <size_t index>
const typename IdToType<index, Ts...>::type& get() const {
if (index != type) {
throw std::bad_variant_access();
}
return *std::launder(reinterpret_cast<typename IdToType<index, Ts...>::type*>(
const_cast<unsigned char*>(data) // const this 只能获取到 const uchar*
));
}
constexpr std::size_t index() const noexcept {
return type;
}
// visit
// 包装一下,以便将 index_seq 传入到形参包 ids
// 注意要继续 forward 传递函数类型,不然 F 可能与传入的 func 不匹配
template <typename F>
auto visit(F&& func) {
return visit2<F>(std::forward<F>(func), std::make_index_sequence<sizeof...(Ts)>{});
}
// 接收一个可调用对象,根据当前 type 调用其对应重载
template <typename F, size_t... ids>
auto visit2(F&& func, std::integer_sequence<size_t, ids...>) {
// 使用 declval 创建 F 实例并获取其 operator (...) 方法的返回值类型
// F 需要能接收所有选项,并返回相同类型。所以可用 T0 获取返回值类型(同样用 declval 构造 T0 对象)
using Ret = decltype(std::declval<F>()(std::declval<T0>()));
using VisitorT = Ret (*)(F&&, Variant&);
// 生成下标传给各函数,以便使用下标 get(类型可以重复,因此使用 Ts... 进行类型 get 可能错误)
// VisitorT 要与 visitImpl 完全匹配(实例化了就没有万能引用了)
static constexpr VisitorT visitorFuncs[] = { visitImpl<F, Variant, ids>... };
return visitorFuncs[type](std::forward<F>(func), *this);
}
private:
size_t type;
// alignas(MaxAlign<Ts...>::value)
alignas(Ts...) unsigned char data[MaxSize<Ts...>::value]; // alignas(T...) 也可展开。对象会取最大的对齐值
using DestroyFuncT = void (*)(unsigned char*);
static constexpr DestroyFuncT destroyFuncs[] = { DestroyImpl<Ts>... };
// 注意不要与类内的 Destroy 定义重名,会优先选择后者
};
// template <typename... Ts>
// const typename Variant<Ts...>::DestroyFuncT Variant<Ts...>::destroyFuncs[] = { DestroyImpl<Ts>... };
// --- 非成员函数
// --- get:通过下标/类型访问 variant 的当前值。
template <size_t I, class... Ts>
variant_alternative_t<I, Variant<Ts...>>& get(Variant<Ts...>& v) {
return v.template get<I>();
}
template <size_t I, class... Ts>
const variant_alternative_t<I, Variant<Ts...>>& get(const Variant<Ts...>& v) {
return v.template get<I>();
}
template <class T, class... Ts>
T& get(Variant<Ts...>& v) {
return v.template get<T>();
}
template <class T, class... Ts>
const T& get(const Variant<Ts...>& v) {
return v.template get<T>();
}
// --- get_if:返回 variant 当前值的指针。若下标/类型与当前值不符,或 pv 是空指针,则返回 nullptr 不抛出异常。
template <size_t I, class... Ts>
constexpr std::add_pointer_t<variant_alternative_t<I, Variant<Ts...>>>
get_if(Variant<Ts...>* pv) noexcept {
if (I != pv->index()) {
return nullptr;
}
return std::addressof(pv->template get<I>());
}
template <size_t I, class... Ts>
constexpr std::add_pointer_t<const variant_alternative_t<I, Variant<Ts...>>>
get_if(const Variant<Ts...>* pv) noexcept {
if (I != pv->index()) {
return nullptr;
}
return std::addressof(pv->template get<I>());
}
template <typename T, class... Ts>
constexpr T* get_if(Variant<Ts...>* pv) noexcept {
constexpr size_t index = TypeToId<T, Ts...>::value;
if (index != pv->index()) {
return nullptr;
}
return std::addressof(pv->template get<T>());
}
template <typename T, class... Ts>
constexpr const T* get_if(const Variant<Ts...>* pv) noexcept {
constexpr size_t index = TypeToId<T, Ts...>::value;
if (index != pv->index()) {
return nullptr;
}
return std::addressof(pv->template get<T>());
}
// ---
struct A {
double a, b;
A(): a(0), b(0) {}
A(int i): a(i), b(i) {}
bool operator ==(const A& x) const {
return a == x.a && b == x.b;
}
};
struct B {
char c;
B() {c=0;}
B(char c): c(c) {}
~B() {}
B(const B& b) {c=b.c;}
B(B&& b) {c=b.c; b.c=0;}
};
template<class... Ts>
struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
int add(int a, int b) {
return a + b;
}
double minus(double a) {
return -a;
}
int main() {
using std::vector;
using std::string;
using std::get;
using std::get_if;
using std::holds_alternative;
using std::visit;
#if false
#define Variant std::variant
#define in_place_type std::in_place_type
#define in_place_index std::in_place_index
#endif
A a{};
B b{'0'};
{
Variant<A, char> v;
assert((size_t)std::addressof(get<0>(v)) % 8 == 0);
assert(alignof(get<0>(v)) == 8 && alignof(v) == 8);
using V = Variant<A, int, B, string>;
V v1;
assert(holds_alternative<A>(v1) && v1.index() == 0 && get<A>(v1) == A{});
assert(!holds_alternative<int>(v1));
V v2{b};
assert(v2.index() == 2 && get<2>(v2).c == '0' && b.c == '0');
V v3{std::move(b)};
assert(v3.index() == 2 && get<2>(v3).c == '0' && b.c == 0);
V v4{"abc"};
assert(v4.index() == 3 && get<3>(v4) == "abc");
using V2 = Variant<A, vector<int>, string, char>;
V2 a {in_place_type<vector<int>>, 3, (int)'a'};
assert(a.index() == 1 && get<1>(a).size() == 3 && get<1>(a)[0] == 'a');
V2 b {in_place_type<string>, 3, 'a'};
assert(b.index() == 2 && get<2>(b) == "aaa");
V2 c {in_place_index<1>, 3, 'a'};
assert(c.index() == 1 && get<1>(c).size() == 3 && get<1>(c)[0] == 'a' && holds_alternative<vector<int>>(c));
V2 d {in_place_index<2>, "ABCDE", 4};
assert(d.index() == 2 && get<2>(d) == "ABCD" && holds_alternative<string>(d));
}
{
Variant<int(*)(int, int), int, double(*)(double)> x = add;
assert(get<0>(x)(1, 2) == 3);
x.emplace<2>(minus);
assert(get<2>(x)(3) == -3);
x = add;
assert(get<0>(x)(2, 3) == 5);
Variant<string, bool, const char*> a = "abc";
assert(a.index() == 2);
// 不存放 const char[4]。"abc" 实际是 const char&[4],也需要 remove_ref
Variant<string, const char*, const char[4]> b = "abc";
assert(b.index() == 1);
// Variant<A, in_place_index_t<1>> e;
// e = in_place_index_t<1>{}; // error
}
// get
{
Variant<int, float> v;
int& i = get<int>(v);
assert(i == 0);
i = 1;
int j = get<0>(v);
assert(j == 1);
// get<3>(v); // 越界会有编译期模板错误
// get_if<double>(&v); // 使用不存在的类型会有编译期模板错误
try { // 抛异常
get<float>(v);
get<1>(v);
}
catch (const std::bad_variant_access&) {
puts("catch");
}
assert(*get_if<0>(&v) == 1);
assert(*get_if<int>(&v) == 1);
assert(get_if<1>(&v) == nullptr);
assert(get_if<float>(&v) == nullptr);
}
// operator =
{
Variant<string, string> v1;
// v1 = string("abc"); // 实现可以这样赋值第一个 string,std 不允许,属于通过类型赋值
get<0>(v1) = "abc";
assert(v1.index() == 0 && get<0>(v1) == "abc");
v1.emplace<1>("def");
assert(v1.index() == 1 && get<1>(v1) == "def");
using V = Variant<A, int, vector<int>, string>;
V v2 {vector<int>{1, 2, 3}};
assert(v2.index() == 2 && !holds_alternative<string>(v2));
v2 = "abc";
assert(v2.index() == 3 && get<string>(v2) == "abc");
v2 = 3;
assert(v2.index() == 1 && get<int>(v2) == 3);
V v3 {1};
assert(v3.index() == 1);
v3 = vector<int>(2); // resize(2)
get<2>(v3).push_back(1);
get<2>(v3).push_back(2);
auto& vec = get<2>(v3);
assert(vec[0] == 0 && vec[2] == 1 && vec[3] == 2);
}
// emplace
{
Variant<string, string> v1;
v1.emplace<0>("abc");
assert(v1.index() == 0 && get<0>(v1) == "abc");
v1.emplace<1>("def");
assert(v1.index() == 1 && get<1>(v1) == "def");
using V = Variant<A, int, vector<int>, string>;
V v2 {vector<int>{1, 2, 3}};
assert(v2.index() == 2 && !holds_alternative<string>(v2));
v2.emplace<string>("abc");
assert(v2.index() == 3 && get<string>(v2) == "abc");
v2.emplace<0>(3);
assert(v2.index() == 0 && get<A>(v2) == A{3});
V v3 {1};
assert(v3.index() == 1);
v3.emplace<2>(2); // resize(2)
get<2>(v3).push_back(1);
get<2>(v3).push_back(2);
auto& vec = get<2>(v3);
assert(vec[0] == 0 && vec[2] == 1 && vec[3] == 2);
}
// else
{
Variant<int, long, string> v = 3l;
if (auto p = get_if<long>(&v)) {
assert(*p == 3l);
} else {
assert(false);
}
v = 5;
if (holds_alternative<long>(v)) {
assert(false);
} else if (holds_alternative<int>(v)) {
assert(get<int>(v) == 5);
}
}
// visit
{
Variant<int, string> v = 9;
auto f = [](auto&& arg) {
cout << "v(9): " << arg << '\n';
};
visit(f, v);
v = "abc";
visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>)
assert(false);
else if constexpr (std::is_same_v<T, string>)
assert(arg == "abc");
else
static_assert(false, "error type");
}, v);
auto val = visit(overloaded{
[](int arg) { assert(arg == 1); return 1; },
[](const string& arg) { assert(arg == "abc"); return 2;},
}, v);
assert(val == 2);
}
// TODO
{
std::variant<float, long, double> b = 0;
assert(b.index() == 1); // 目前实现的结果将是 0,float/double/char 都不应匹配 int,要换一种匹配方式
}
// type check
using i2t1 = IdToType<0, double, int, float, char[3]>::type;
using i2t2 = IdToType<2, double, int, float, char[3]>::type;
using i2t3 = IdToType<3, double, int, float, char[3]>::type;
// using f = FirstType<int, double, A[2]>::type;
[](...) {}(
MaxSize<int, char, double, char[3], A[2]>::value,
MaxAlign<int, char, A, char[3], A[2]>::value,
TypeToId<int, int, char[3], double, A>::value,
TypeToId<char[3], int, char[3], double, A>::value,
TypeToId<A[2], int, char[3], double, A[2]>::value,
TypeToId<A[2], int, char[3], double, A>::value,
TypeToId<const char*, std::string>::value,
ConvertTypeToId<const char*, int, std::string>::value,
ConvertTypeToId<const char*, std::string, std::string>::value,
// std::is_same_v<const char*, const char[4]>, // false
TypeToId<const char[4], std::string, const char*, const char[4]>::value, // const char[4] 无意义,里面不需要放数组。此时直接匹配 const char*
TypeToId<const char*, std::string, const char*>::value,
ConvertTypeToId<const char[4], std::string, const char*>::value, // 实际应该是 1,判断时需用 decay(T)
TypeToId<decltype(0), float, long, double>::value,
ConvertTypeToId<decltype(0), float, long, double>::value, // 将匹配第一个 float,实际应该是 long
T_is_instantiation_of<in_place_type_t, in_place_type_t<int>>::value,
T_is_instantiation_of<in_place_type_t, in_place_index_t<1>>::value,
V_is_instantiation_of<in_place_index_t, in_place_index_t<1>>::value
// CheckAll<std::is_copy_constructible, A, int, B>::value
);
}
std::any
#include <any>
#include <cstdio>
#include <vector>
#include <iostream>
#define pc putchar
using std::cout;
using std::string;
using std::vector;
template<typename T>
std::ostream& operator << (std::ostream& out, const vector<T>& v) {
out << "[";
for (auto it = v.begin(); it != v.end(); it++) {
if (it != v.begin()) out << ", ";
out << *it;
}
out << "]";
return out;
}
class Any {
public:
Any(): data(nullptr) {}
Any(const Any& other) {
if (other.data) {
data = other.data->clone();
}
}
Any(Any&& other): data{other.data} {
other.data = nullptr;
}
Any& operator =(const Any& other) {
if (this == &other) {
return *this;
}
reset();
if (other.data) {
data = other.data->clone();
}
return *this;
}
Any& operator =(Any&& other) {
reset();
data = other.data;
other.data = nullptr;
return *this;
}
template <typename T,
typename = std::enable_if_t<!std::is_same_v<Any, std::decay_t<T>>>>
Any(T&& v): data(new Data<std::decay_t<T>>(std::forward<T>(v))) {}
// 注意 Data 的模板上不能带引用,不然就不能用Data<T>准确获取了。但为了完美转发,构造函数上的T还是要带引用
// 此外 Data 的成员 data 的类型也不能带引用,否则传左值时它就是 T&,会获取引用,而不是进行拷贝,可能导致悬垂引用!
// 但与 remove_reference 相比,decay 更合适,见 function
~Any() {
reset();
}
template <typename T>
T& any_cast() {
auto p = data == nullptr ? nullptr : dynamic_cast<Data<T>*>(data);
if (p == nullptr) {
throw std::bad_any_cast();
}
return p->data;
// return ((Data<T>*)data)->data;
}
void reset() noexcept {
delete data;
}
void swap(Any& other) noexcept {
std::swap(data, other.data);
}
const std::type_info& type() const noexcept {
return data ? data->type() : typeid(nullptr);
}
friend std::ostream& operator << (std::ostream& out, const Any& any);
private:
struct Base {
virtual ~Base() {}
virtual Base* clone() const = 0;
virtual const std::type_info& type() const noexcept = 0;
virtual std::ostream& print(std::ostream& out) = 0;
};
Base* data;
template <typename T>
struct Data: Base {
template <typename UT>
Data(UT&& v): data(std::forward<UT>(v)) {
static_assert(std::is_same_v<T, std::decay_t<UT>>);
}
// 为了实现完美转发,构造函数中的模板必须带引用;但类本身的模板不能带引用,所以只能写两个模板参数
Base* clone() const override {
return new Data<T>(*this);
}
const std::type_info& type() const noexcept override {
return typeid(T);
}
std::ostream& print(std::ostream& out) override { // 类型需要重载 operator <<
out << data;
return out;
}
T data;
};
};
std::ostream& operator << (std::ostream& out, const Any& any) {
if (any.data) {
any.data->print(out);
} else {
out << "null";
}
return out;
}
struct Test {
Test() { x = 0; debug && puts("Test()");}
~Test() { debug && printf("~Test(%d)\n", x);}
Test(const Test& o) { x = o.x; debug && puts("Test(const Test&)");}
Test(Test&& o) { x = o.x; o.x = 0; debug && puts("Test(&&)");}
int x;
static constexpr bool debug = false;
};
int main() {
if (true) {
vector<Any> vec{1, 2.0, 3.f, "abc", vector<Any>{'a', "str"}};
cout << vec << '\n';
vec[2] = vector<int>{3, 3};
cout << vec << '\n';
}
if (false) {
Any a = Test();
cout << a.any_cast<Test>().x << '\n'; // 0
}
if (false) {
Test t;
t.x = 1;
Any b = t;
cout << b.any_cast<Test>().x << '\n'; // 1
t.x = 2;
cout << b.any_cast<Test>().x << '\n'; // 1
b.any_cast<Test>().x *= -1;
Test t2 = b.any_cast<Test>();
cout << t2.x << '\n'; // -1
// copy
Any c = b;
Test& t3 = c.any_cast<Test>();
cout << t3.x << '\n'; // -1
t3.x --;
cout << c.any_cast<Test>().x << '\n'; // -2
Any d = std::move(c);
try {
cout << d.any_cast<Test>().x << '\n'; // -2
cout << c.any_cast<Test>().x << '\n'; // error
}
catch(const std::exception& e) {
std::cerr << e.what() << '\n';
}
}
if (true) {
Any a = 5;
Any b = 1.f;
Any c = std::vector<int>{1, 2};
cout << a.type().name() << '\n';
cout << b.type().name() << '\n';
cout << c.type().name() << '\n';
}
cout << "end\n";
}
std::function
#include <cstdio>
#include <iostream>
#include <functional>
using std::cout;
// class bad_function_call: public std::exception {
// virtual const char* what() const noexcept {
// return "bad function call";
// }
// };
template <typename>
class Function;
template <typename R, typename... Args>
class Function<R(Args...)> {
public:
Function(): callable(nullptr) {}
Function(const Function& other) {
if (other.callable) {
callable = other.callable->clone();
}
}
Function(Function&& other): callable(other.callable) { // 注意这里不要clone,直接获取值
other.callable = nullptr;
}
template <typename F,
typename = std::enable_if_t<!std::is_same_v<Function, std::decay_t<F>>>>
Function(F&& f): callable(new NormalCallable<std::decay_t<F>>(std::forward<F>(f))) {}
// 模板类型不能带引用,否则会产生悬垂引用,见 any
// 但去除引用后,NormalCallable 的 F 会被推导为原类型,赋值函数时将是函数类型
// 而数据成员的类型不能是函数类型。使用 decay 而非 remove_reference 刚好可做到这点(对于函数类型,会转为函数指针)
// 但是 decay 后 Function g = f; 会匹配到这个模板构造函数,而非 Function(const Function&),除非加 enable_if 或 Function(Function&)
template <typename C, typename Func>
Function(Func C::* f): callable(new MemberFunctionPointer<C, Func>(std::forward<Func C::*>(f))) {}
~Function() {
delete callable;
}
operator bool() const noexcept {
return this->callable != nullptr;
}
R operator()(Args... args) {
if (static_cast<bool>(*this) == false) {
throw std::bad_function_call();
}
return callable->invoke(std::forward<Args>(args)...);
}
void swap(Function &other) noexcept {
std::swap(callable, other.callable);
}
private:
// 抽象基类,用于存储可调用对象的指针
class BaseCallable {
public:
virtual R invoke(Args... args) = 0;
virtual ~BaseCallable() {}
virtual BaseCallable* clone() const = 0;
};
BaseCallable* callable;
// 具体实现类,用于存储特定类型的可调用对象
// #1 存储普通函数指针、仿函数、lambda、bind
template <typename F>
class NormalCallable : public BaseCallable {
public:
template <typename T>
NormalCallable(T&& f) : func(std::forward<T>(f)) {
// 对于仿函数,F为仿函数类型,T为 F&& 或 F&
// 对于普通函数,F为函数指针,T为函数类型(可能带引用)
static_assert(std::is_same_v<F, std::decay_t<T>>);
}
R invoke(Args... args) override {
return func(std::forward<Args>(args)...);
}
BaseCallable* clone() const override {
return new NormalCallable(*this);
}
private:
F func;
};
// #2 存储函数成员指针
template <typename C, typename F>
class MemberFunctionPointer : public BaseCallable {
public:
MemberFunctionPointer(F C::*f) : func(f) {}
R invoke(Args... args) override {
return invokeImpl(args...);
}
template <typename... Args2>
R invokeImpl(C& object, Args2... args) {
return (object.*func)(std::forward<Args2>(args)...);
}
template <typename... Args2>
R invokeImpl(const C& object, Args2... args) {
return (object.*func)(std::forward<Args2>(args)...);
}
BaseCallable* clone() const override {
return new MemberFunctionPointer(*this);
}
private:
F C::*func;
};
};
struct Node {
Node(int x): x(x) {}
int f(int v) {
cout << "Node::f(" << v << ") x:" << x << '\n';
return x;
}
int const_f(int v) const {
cout << "Node::const_f(" << v << ") x:" << x << '\n';
return x;
}
int x;
};
struct Func {
int operator()(int x) {
return x + 1;
}
};
int add(int x) {
return x + 1;
}
int add2(int x, int y) {
return x + y;
}
int main() {
// #define Function std::function
// #1 存储普通函数指针
Function<int (int)> f_add = add;
cout << f_add(1) << '\n'; // 2
Function<int (int)> f_add_c = f_add;
cout << f_add_c(1) << '\n'; // 2
// 通过 clone 创建新的 callable
auto f_add2 = std::move(f_add);
cout << f_add2(2) << '\n'; // 3
// cout << f_add(2) << '\n'; // bad_function_call
// #2 存储成员函数指针
Node node{1};
Function<int (Node&, int)> fp = &Node::f; // 通过Node&实例调用(也可以是const Node&)
cout << fp(node, 2) << '\n'; // 1
Function<int (const Node&, int)> fpc = &Node::const_f; // 通过const Node&实例调用
cout << fpc(1, 2) << '\n'; // 1 隐式构造的右值可以绑定到const左值引用
// 通过 clone 创建新的 callable
auto fp2 = fp;
cout << fp2(node, 3) << '\n'; // 1
// #3 (不支持)存储数据成员指针
// Function<int& (Node&)> fr = &Node::x; // 也可以是 int (const Node&)
// cout << fr(node) << '\n'; // 1
// fr(node) = 3;
// cout << node.x << '\n'; // 3
// #4 存储仿函数
Function<int (int)> ff = Func();
cout << ff(3) << '\n'; // 4
// #5 存储lambda
int tmp = 1;
auto lam = [&tmp](int x) {
tmp += x;
return tmp;
};
Function<int (int)> f_lam = lam;
cout << f_lam(2) << '\n'; // 3
cout << tmp << '\n'; // 3
// #6 存储bind表达式
Function<int (int)> f_bind = std::bind(add2, 2, std::placeholders::_1);
cout << f_bind(3) << '\n'; // 5
}
function_ref
#include <cassert>
#include <iostream>
#include <type_traits>
using std::cout;
/*
类似 string_view,可以设计不包含函数所有权、只是简单使用的轻量级 function_ref,能够赋值任何可调用对象,但比 function 高效轻巧。
它不负责函数捕获变量的生命周期,一定要在引用值的生命周期内使用。
void* 可以保存一切指针,但还需要保存、恢复函数的类型信息。
而模板参数会作为模板定义类型的一部分,以便使用,因此可以保存一个模板函数或模板类,将函数类型放在模板参数上,模板内部将 void* 指针转为模板参数(即原本函数)的类型返回或直接调用。
TODO:
无法保存成员函数指针,需要像 Function 一样对其做特化,因为不能用 f(args) 调用,需要 .*。
如果保存的只是函数指针,可以优化为 8B。
*/
template <typename>
class FunctionRef;
template <typename Ret, typename... Args>
class FunctionRef<Ret(Args...)> { // 除了无所有权外,与 function 类似
private:
using ConverterType = Ret(*)(void*, Args...);
void* p;
ConverterType converter;
template <typename Func>
static Ret ConvertAndCall(void* p, Args... args) {
auto f = *reinterpret_cast<Func*>(p);
return f(std::forward<Args>(args)...);
}
public:
FunctionRef(): p(nullptr), converter(nullptr) {}
template <typename Func>
FunctionRef(Func&& f) {
p = reinterpret_cast<void*>(&f);
converter = ConvertAndCall<std::remove_reference_t<Func>>; // 保存实例化(包含了类型信息)的模板;注意参数 Func 是带引用的
}
/*
Args 是所存函数形参的类型(最初的模板参数),而非推断出的实参类型。
虽然没有万能引用,但这里使用 forward 也能正确转发 args:
- 当 Args 是 A 时,args 是值拷贝的临时量,forward<A>(a) 等价于 move(a) 使用右值传递
- 当 Args 是 A& 时,forward<A>(a) 折叠后依旧使用 A& 传递
- 当 Args 是 const A& 时,forward<A>(a) 依旧使用 const A& 传递
*/
Ret operator ()(Args... args) {
return converter(p, std::forward<Args>(args)...);
}
operator bool() {
return p != nullptr;
}
};
// ---
int sum(int a, int b) {
return a + b;
}
int mul(int a, int b) {
return a * b;
}
int foo(FunctionRef<int(int, int)> fRef, int x, int y) {
return fRef(x, y);
}
struct A {
int f(int x) {
return x * 2;
}
int operator ()(int x) {
return x + 1;
}
};
int main() {
// 普通函数
FunctionRef<int(int, int)> f1 = sum;
assert(f1(3, 5) == 8);
f1 = mul;
assert(f1(3, 5) == 15);
// lambda
int cnt = 1;
auto lambda = [&](int x, int y) {cnt+=x+y; return cnt; };
assert(foo(lambda, 1, 2) == 4 && cnt == 4);
f1 = lambda;
assert(f1(1, 2) == 7 && cnt == 7);
A a;
// 不支持成员函数
// FunctionRef<int(A&, int)> af = &A::f;
// assert(af(a, 3) == 6);
// 仿函数
FunctionRef<int(int)> af2 = a;
assert(af2(3) == 4 && a(3) == 4);
}
std::make_integer_sequence O(logn)
#include <type_traits>
template <class T, T... Ints>
struct integer_sequence {};
// --- 常规 O(n) 实现,每次向可变参数中添加一个 N-1,最后用它构造整个 sequence
namespace impl1 {
template <class T, T N, T... Ints>
struct make_integer_sequence_impl {
using type = make_integer_sequence_impl<T, N-1, N-1, Ints...>::type; // 添加 N-1 到 Ints
};
// 在递归到0时,make_integer_sequence_impl 的形参包拥有 0~N-1
// 如果不进行约束,需要将 N 和 Ints 设为 size_t 类型,才能写 make<T, 0> 特化(否则类型会依赖 T,不能直接用0)
template <class T, T N, T... Ints>
requires (N == 0)
struct make_integer_sequence_impl<T, N, Ints...> {
using type = integer_sequence<T, Ints...>;
};
template <class T, T N>
using make_integer_sequence = make_integer_sequence_impl<T, N>::type;
using seq3 = make_integer_sequence<int, 3>;
static_assert(std::is_same_v<seq3, integer_sequence<int, 0, 1, 2>>);
} // namespace impl1
// --- 常规 O(n) 实现,每次向可变参数中添加一个 N-1,最后用它构造整个 sequence
namespace impl2 {
// 继承 conditional 而非直接使用 type = conditional<..., make::type>,可以避免 conditional 因为使用到了 make 的 type 成员而实例化 make 导致无限递归
template <class T, T N, T... Ints>
struct make_integer_sequence_impl:
std::conditional_t<
(N == 0),
std::type_identity<integer_sequence<T, Ints...>>,
make_integer_sequence_impl<T, N-1, N-1, Ints...>> {}; // 添加 N-1 到 Ints
template <class T, T N>
using make_integer_sequence = make_integer_sequence_impl<T, N>::type;
using seq3 = make_integer_sequence<int, 3>;
static_assert(std::is_same_v<seq3, integer_sequence<int, 0, 1, 2>>);
} // namespace impl2
// --- O(log n) 实现。分治,每次将 make(N) 变成两个 make(N/2),生成两个 0~N/2-1 的模板(它们是同一个模板),然后利用包展开为后者的每个模板值加 N/2,合并成 make(N)
// 在递归到 1 时,make(1) 的形参包拥有 0;在递归到 2 时,make(2) 为 merge(make(1)) 即 0, 1;递归到 N 时,make(N) 为 merge(make(N/2)) 即 0, ..., N/2-1, N/2, ..., N-1
namespace impl3 {
template <class T, T N, T... Ints>
struct make_integer_sequence_impl;
template <class Seq, auto val>
struct double_sequence;
template <class T, T... Ints, T val>
struct double_sequence<make_integer_sequence_impl<T, Ints...>, val> {
using type =
std::conditional_t<val == 0,
make_integer_sequence_impl<T, Ints..., (Ints + sizeof...(Ints))...>,
make_integer_sequence_impl<T, Ints..., (Ints + sizeof...(Ints))..., sizeof...(Ints) * 2>
>; // 当 N 为奇数时,在 make 最后附加一个数
};
template <class T, T N, T... Ints>
struct make_integer_sequence_impl {
using type = double_sequence<typename make_integer_sequence_impl<T, N/2>::type, N&1>::type;
};
template <class T, T N>
requires (N == 1)
struct make_integer_sequence_impl<T, N> {
using type = make_integer_sequence_impl<T, 0>;
};
// 在用一个类将 make(Ints...) 转为 integer_sequence<Ints...>。可以在 make 里定义,但这会实例化多个 integer_sequence
template <class T>
struct make_integer_sequence_converter;
template <class T, T... Ints>
struct make_integer_sequence_converter<make_integer_sequence_impl<T, Ints...>> {
using type = integer_sequence<T, Ints...>;
};
template <class T, T N>
using make_integer_sequence = make_integer_sequence_converter<typename make_integer_sequence_impl<T, N>::type>::type;
using seq6 = make_integer_sequence<int, 6>;
using seq11 = make_integer_sequence<int, 11>;
static_assert(std::is_same_v<seq6, integer_sequence<int, 0, 1, 2, 3, 4, 5>>);
static_assert(std::is_same_v<seq11, integer_sequence<int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>);
} // namespace impl3
int main() {
}
状态元编程
https://zhuanlan.zhihu.com/p/646752343
STMP 未被标准支持,可能是非良构的,并且容易导致 odr violation(违反 ODR),比如:如果 A, B 都使用了 STMP 实现的计数器,C 先 include A 再 B,D 先 include B 再 A,那么编译后的 C 和 D 中的 A, B 得到的计数器值是不同的(注意 include 是文本替换,再编译)。
C++ 常规的常量表达式求值既不依赖、也不能改变全局的状态,它们类似一个纯函数,只要输入确定,输出就确定。模板元编程也是如此。
状态元编程 (stateful template meta programming) 能够在编译期的常量求值中引入全局状态,使同样的常量表达式可以求值出不同结果。
比如:实现一个编译期计数器:
constexpr int a = f();
constexpr int b = f();
constexpr int c = f();
static_assert(a == 1 && b == 2 && c == 3); // 常规的模板变成无法实现,f 将始终得到相同结果
STMP 的原理:通过控制模板实例化来控制函数何时定义;根据函数是否定义返回不同的值。
友元函数可以在类外部声明,在类内部定义;如果这个类是一个类模板,那么只有在实例化类时才会定义该函数:
auto flag();
template <bool val> // 任意模板参数
struct flag_setter {
friend auto flag() {}
};
int main() {
// flag(); // error,无法推导 auto
flag_setter<true>{};
flag(); // ok,实例化了 flag_setter 并定义了 flag
}
注意函数的返回类型需要为 auto,这样当不存在类的实例化和 flag 的定义时,调用 flag 会因为没有定义、无法确定返回值类型而先推导失败(属于 SFINAE,不是严重错误);如果不是 auto 而是固定类型,则会因为没有定义而直接导致编译失败(不是推导失败,属于错误),并且依然能找到之后实例化后的 flag。(不确定?)
推导失败不是错误,仍可继续执行;推导时只会考虑当前位置及以前的函数定义,之后再定义函数不会影响 auto 推导和找不到定义。函数未定义是错误,直接导致编译失败;检查函数定义时会检查整个翻译单元中是否有该函数。
通过这个逻辑并检查flag()
是否是合法表达式,就可以实现0到1的计数器:
auto flag();
template <bool val>
struct flag_setter {
friend auto flag() {}
};
template <bool condition = requires { flag(); }>
consteval int value() {
if constexpr(!condition) {
constexpr flag_setter<false> tmp;
return 0;
} else {
return 1;
}
}
int main() {
constexpr auto a = value();
constexpr auto b = value();
static_assert(a == 0 && b == 1);
}
模板实例化时会进行两阶段名字查找:在第一阶段检查语法是否正确;在第二阶段(即实例化时)替换模板参数,再检查是否正确。
(某些编译器将所有检查放在第二阶段)
所以某些不依赖模板参数的错误,在实例化前就可以被找到,导致 CE。
所以上述代码可能要修改flag()
,让它依赖待决名,避免直接因为 flag 未定义而报错:auto flag(int); template <bool val> struct flag_setter { friend auto flag(int) {} }; template <int arg = 0, bool condition = requires { flag(arg); }> // arg 可以是任意模板参数 consteval int value() { ... }
通过类模板reader<N>
将友元函数 flag 扩展到 N 个(每个 reader 有一个),然后检查flag(reader<N>{})
是否定义,就可以实现 0~N-1 的计数。
template <size_t N>
struct reader {
friend auto flag_counter(reader<N>); // 声明
};
template <size_t N>
struct setter {
friend auto flag_counter(reader<N>) {} // 定义
};
template <size_t N = 0,
auto tag = []{},
bool condition = requires { flag_counter(reader<N>{}); }>
consteval auto next() {
if constexpr (!condition) {
constexpr setter<N> tmp; // 定义 flag(reader<N>)
return N;
} else { // 注意这里要 else,如果没有,也会在 condition 为 false 时生成该分支代码
return next<N + 1>(); // 可以优化成指数级增长和检查
}
}
int main() {
constexpr auto a = next();
constexpr auto b = next();
constexpr auto c = next();
static_assert(a == 0 && b == 1 && c == 2);
}
这里 flag 是在类内声明和定义的友元函数,只能通过 ADL 找到,也不会影响命名空间。
注意函数 next 中需要一个默认参数auto tag = []{}
。
常量表达式是纯的,即如果函数的各实参都相同,它的结果也应当相同,所以编译器可以缓存对同一函数的调用结果,避免多次调用,不符合我们的预期。因为不同位置创建的 lambda 都是不同的类型(即使看起来相同),并且函数的默认参数会在每次执行时求值,所以使用一个 lambda 作为默认模板实参,可以在每次调用函数时产生不同的模板实参、不同的函数和常量表达式,从而避免编译器缓存结果。
使用source_location::line
作为默认实参,也可以实现同样的效果。
STMP 实现 Any
在用 STMP 实现计数器时,是通过一个每次调用都有不同模板实参的模板函数 next,而不同 next 的返回值也不需要相同,可以用这个函数做一个类型列表:只需要将原本的实例化 f(reader<N>) 映射到计数 N
更改为实例化 f(reader<N>) 映射到类型 TN
,就可以创建一个可自行添加的类型列表,并可通过下标查询;通过在 setter 上额外附近一个类型 T 记录当前的类型列表,就可以在任意类型列表的基础上添加类型(换句话说就是可以删除类型)。
通过这个类型列表可以实现自行注册和查询类型的 Any,在使用时不需要 any_cast 自行转换,可以直接通过 visit 读取。
但与普通 TMP 实现的 typelist 相比,区别是?
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------