Skip to content

arqady01/cpp_template_tips

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 

Repository files navigation

基于类型属性的重载

布局1

算法特化

算法特化不是函数特化,况且函数不支持特化,函数只支持重载。

函数模板重载的一个动机是,基于算法适用的类型信息,为算法提供更加特化的版本。

在一个泛型算法中引入更为特化的变体,这一设计方式被称为算法特化。

//举个例子
template<typename InputIterator, typename Distance>
void advanceIter(InputIterator& x, Distance n) {
    while (n > 0) { //线性时间
        ++x;
        --n;
    }
}
//如果传进来的迭代器支持随机访问,就可以考虑算法特化
template<typename RandomAccessIterator, typename Distance>
void advanceIter(RandomAccessIterator& x, Distance n) {
    x += n; //固定时间
}

不幸的是,同时定义以上两种函数模板会导致编译错误,因为虽然函数模板支持重载,但是只有模板参数名字不同,是不能重载的

标记派发

算法特化的一个方式是,可以用一个唯一的、可以区分特定类型来标记不同算法变体的实现。比如为了解决上述 advanceIter() 中的问题,可以用STL的迭代器种类类型来区分 advanceIter() 算法的两个变体实现:

template<typename Iterator, typename Distance>
void advanceIterImpl(Iterator& x, Distance n, std::input_iterator_tag) {
    while (n > 0) { //线性时间
        ++x;
        --n;
    }
}
template<typename Iterator, typename Distance>
void advanceIterImpl(Iterator& x, Distance n, std::random_access_iterator_tag) {
    x += n; //固定时间
}
//然后通过 advanceIter() 函数模板将其参数连同与之对应的 tag 一起转发出去:
template<typename Iterator, typename Distance>
void advanceIter(Iterator& x, Distance n) {
    advanceIterImpl(x, n, typename std::iterator_traits<Iterator>::iterator_category())
}

萃取类模板 std::iterator_traits 通过其成员类型 iterator_category 返回了迭代器的种类。有效使用标记派发的关键在于理解 tags 之间的内在关系:

struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag{ };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };

Enable/Disable 函数模板

C++为之提供了一个辅助工具是 std::enable_if,为了避免名称冲突,我们将称之称为 EnableIf

随机访问版本的 advanceIter() 算法可以被实现成这样:

//先复习一下IsConvertible
template<typename FROM, typename TO>
struct IsConvertibleHelper {
private:
    static void aux(TO to);
    //尝试将FROM模板参数传递给F
    template<typename F, typename T, typename = decltype( aux( std::declval<F>() ) )>
    static std::true_type test(void*); //
    // test() fallback:
    template<typename, typename>
    static std::false_type test(…); //
public:
    using Type = decltype(test<FROM>(nullptr)); //
};

template<typename FROM, typename TO>
struct IsConvertibleT : IsConvertibleHelper<FROM, TO>::Type {
};
template<typename FROM, typename TO>
using IsConvertible = typename IsConvertibleT<FROM, TO>::Type;
template<typename FROM, typename TO>
constexpr bool isConvertible = IsConvertibleT<FROM, TO>::value;
template <typename Iterator>
constexpr bool IsRandomAccessIterator = 
    IsConvertible<typename std::iterator_traits<Iterator>::iterator_category,
        std::random_access_iterator_tag>;
template<typename Iterator, typename Distance>
EnableIf<IsRandomAccessIterator<Iterator>> advanceIter(Iterator& x, Distance n) {
    x += n; //固定时间
}

EnableIf 的实现很简单:

template<bool, typename T = void>
struct EnableIfT { };
template< typename T>
struct EnableIfT<true, T> {
    using Type = T;
};
template<bool Cond, typename T = void>
using EnableIf = typename EnableIfT<Cond, T>::Type;

EnableIf 通常被用于函数模板的返回类型

模板和继承

空基类优化 EBCO

就算是空的类,所占用的内存大小也不是零,而是一:

class EmptyClass { };
int main() {
    std::cout << sizeof(EmptyClass); //1
}

当空类被用作基类的时候,如果不给它分配内存并不会导致其被存储到与其它同类型对象或者子对象相同的地址上,那么就可以不给它分配内存。

空基类优化的意义:

class Empty {
    using Int = int; //类型别名成员不会使类变为非空
};
class EmptyToo : public Empty { };
class EmptyThree : public EmptyToo { };

int main() {
    std::cout << "sizeof(Empty): " << sizeof(Empty);
    std::cout << "sizeof(EmptyToo): " << sizeof(EmptyToo);
    std::cout << "sizeof(EmptyThree): " << sizeof(EmptyThree);
}
  • 如果编译期实现了 EBCO,那么 EmptyToo 和 EmptyThree 的大小和 Empty 是一样的
    • 布局1
  • 如果编译期没有实现EBCO,那么大小将会递增
    • 布局2

CRTP 奇异的递归模板模式

CRTP 指的是“将派生类作为模板参数传递给某个基类”

template<typename Derived> //基类
class CuriousBase { ...
};
class Curious : public CuriousBase<Curious> { ...
};

改造一下,将派生类实现为模板类:

template<typename Derived>
class CuriousBase { ...
};
template<typename T>
class CuriousTemplate : public CuriousBase<CuriousTemplate<T>> { ...
};

将派生类 CuriousTemplate 作为模板参数传递给基类,基类可以在不使用虚函数(类中不允许虚函数模板)的情况下定制派生类的行为

一个简单的CRTP例子,将其用于追踪从一个class类型实例化出多少个对象(这个功能也可以通过在ctor中递增一个static成员,在dtor中递减即可实现,不过这做法很boring)

template <typename CountedType>
class ObjectCounter {
private:
    inline static std::size_t count = 0; //实例化对象的计数器
protected:
    ObjectCounter() { ++count; } //ctor
    ObjectCounter(ObjectCounter<CountedType> const&) {
        ++count; //copy ctor
    }
    ObjectCounter(ObjectCounter<CountedType> &&) {
        ++count; //move ctor
    }
    ~ObjectCounter() { --count; } //dtor
public:
    static std::size_t live() {
        return count;
    }
};
  • 类中 不会变的 static 成员,可用 constexpr 修饰,无需类外初始化
  • 类中 会变的 static 成员,可用 inline 修饰即可类内完成初始化

当想要统计某一个 class 对象的数目时,只需要让其继承 ObjectCounter,比如可以按下面的方式统计 MyString 对象数目:

template<typename CharT>
class MyString : public ObjectCounter<MyString<CharT>> {};
int main() {
    MyString<char> s1, s2;
    MyString<wchar_t> ws;
    std::cout << MyString<char>::live() << std::endl; //2
    std::cout << ws.live() << std::endl; //1
}

The Barton-Nackman Trick

1994年, Barton和Nackman提出了“restricted template expansion”技术,因为在当时,函数模板严重受限,而且namespace也不受支持

假设需要为类模板Array定义 operator==,一个可能的方案是将“运算符==”定义为类模板的成员,因为希望 operator== 中参数是对称的,因此更倾向与将其定义为某一个namespace 中的函数:

//Array类的定义
template<typename T>
class Array { ... };

template<typename T>
bool operator== (Array<T> const& a, Array<T> const& b) { //参数对称
    ...
}

因为函数模板不可以被重载,所以引入了新问题:当前作用域内不可以再声明其它的 operator== 模板,而其它的类模板却又很可能需要这样一个类似的模板。Barton和Nackman 通过将 operator== 定义成 class 内部的友元函数解决了这一问题:

template<typename T>
class Array {
    static bool areEqual(Array<T> const& a, Array<T> const& b);
public:friend bool operator== (Array<T> const& a, Array<T> const& b) {
        return areEqual(a, b);
    }
};

假设我们用 float 实例化 Array 类,作为实例化的结果,该友元运算符函数也会被连带声明,但是注意该函数本身并不是一个函数模板的实例。

在 modern C++中,相比于直接定义一个函数模板,在类模板中定义一个 friend 函数的好处是:友元函数可以访问该类模板的 private 成员以及 protected 成员,并且无需再次申明该类模板的所有模板参数。

但是,在与 CRTP 结合之后,friend 函数定义可以变的更有用一些,就如在下面一节中节介绍的那样。

运算符的实现

在给一个类重载运算符的时候,通常也需要重载一些其它的(当然也是相关的)运算符。

比如,一个实现了 operator== 的类,通常也会实现 operator !=,一个实现了 operator< 的类,通常也会实现其它的关系运算符(>,<=,>=)。通常情况下,这些运算符中只有一个运算符的定义比较有意思,其余的运算符都可以通过它来定义。

例如,类 X 的 operator != 可以通过使用 operator== 来定义:

bool operator!= (X const& x1, X const& x2) {
    return !(x1 == x2);
}

对于 operator != 的定义类似的类型,可以通过模板将其泛型化:

template<typename T>
bool operator!= (T const& x1, T const& x2) {
    return !(x1 == x2);
}

基于 CRTP 的运算符模板则允许程式去选择泛型的运算符定义:

template<typename Derived>
class EqualityComparable {
public:
    friend bool operator!= (Derived const& x1, Derived const& x2) {
        return !(x1 == x2);
    }
};

class X : public EqualityComparable<X> {
public:
    friend bool operator== (X const& x1, X const& x2) {
        //实现比较两个 X 类型对象的逻辑
    }
};
int main() {
    X x1, x2;
    if (x1 != x2) { }
}

此处结合使用了 CRTP 和 Barton-Nackman 技术。EqualityComparable<>为了实现派生类中定义的 operator==给其派生类提供 operator !=,使用了 CRTP。事实上这一定义是通过friend函数定义的形式提供的(Barton-Nackman 技术),这使得两个参数在类型转换时的operator !=行为一致。

再看一个例子:

//定义一个基类模板 MathOperations,其中包含一些虚拟的运算符定义
template<typename Derived>
class MathOperations {
public:
	// 加法运算符
	Derived operator+(const Derived& other) const {
        //一定要转为Derived,否则找不到add()函数
		return static_cast<const Derived&>(*this).add(other);
	}
	// 减法运算符
	Derived operator-(const Derived& other) const {
		return static_cast<const Derived&>(*this).subtract(other);
	}
};
//定义派生类Integer,继承自 MathOperations 并实现具体的 add 和 subtract 方法:
class Integer : public MathOperations<Integer> {
public:
	int value;
	
	Integer(int val) : value(val) {}
	Integer add(const Integer& other) const {
		return Integer(value + other.value);
	}
	Integer subtract(const Integer& other) const {
		return Integer(value - other.value);
	}
};

基类 MathOperations 提供了算术框架,具体的运算由子类 Integer 提供。

通过 CRTP,我们能够在编译时就确定这些运算符的行为,且无需虚函数的开销

最后可以用 Integer 类来进行加减运算:

	Integer a(10);
	Integer b(20);
	Integer c = a + b; // 使用由 CRTP 提供的运算符
	Integer d = a - b; // 使用由 CRTP 提供的运算符
	
	std::cout << "c.value = " << c.value << std::endl; // 输出 30
	std::cout << "d.value = " << d.value << std::endl; // 输出 -10

特别之处:

  • 静态多态:通过CRTP实现的多态是静态的,多态行为是在编译时而不是运行时解析的。意味着无需虚函数表,从而可以减少运行时的开销。
  • 类型安全:CRTP通过模板机制确保了类型安全。派生类在编译时就明确了类型信息,由于模板的两步检查可以确保安全
  • 代码复用:基类模板MathOperations定义了操作符的通用模式,派生类Integer只需要实现具体的加减方法
  • 效率:由于操作符是在编译时通过模板解析的,因此执行效率高,没有运行时解析虚函数的额外成本。

Facades

为了展示 facade 模式,我们为迭代器实现了一个 facade,这样可以大大简化一个符合标准库要求的迭代器的编写。一个迭代器类型(尤其是 random access iterator)所需要支持的接口是非常多的。下面的一个基础版的 IteratorFacade 模板展示了对迭代器接口的要求:

template<typename Derived, typename Value, typename Category,
                 typename Reference = Value&, typename Distance = std::ptrdiff_t>
class IteratorFacade {
public:
    using value_type = typename std::remove_const<Value>::type;
    using reference = Reference;
    using pointer = Value*;
    using difference_type = Distance;
    using iterator_category = Category;
    
    //input_iterator 输入迭代器的接口:
    reference operator*() const { … }
    pointer operator->() const { … }
    Derived& operator++() { … }
    Derived operator++(int) { … }
    friend bool operator== (IteratorFacade const& lhs, IteratorFacade const& rhs) { … }
    //bidirectional_iterator 双向迭代的器接口:
    Derived& operator--() { … }
    Derived operator--(int) { … }
    // random_access_ iterator 随机访问迭代器的接口:
    reference operator[](difference_type n) const { … }
    Derived& operator+= (difference_type n) { … }
    friend difference_type operator- (IteratorFacade const& lhs, IteratorFacade const& rhs) { … }
    friend bool operator< (IteratorFacade const& lhs, IteratorFacade const& rhs) { … }
};

我们可以从这些接口中提炼出一些核心的运算符:

  • 对于所有的迭代器,都有如下运算符:
    • 解引用:访问由迭代器指向的值(通常是通过 operator* & ->
    • 递增:移动迭代器以让其指向序列中的下一个元素
    • 相等:判断两个迭代器指向的是不是序列中的同一个元素
  • 对于双向迭代器,还有:
    • 递减:移动迭代器以让其指向列表中的前一个元素
  • 对于随机访问迭代器,还有:
    • 前进:将迭代器向前或者向后移动 n 步
    • 测距:测量一个序列中两个迭代器之间的距离

Facade 的作用是给一个只实现了核心运算符的类型提供完整的迭代器接口。IteratorFacade 的实现就涉及到到将迭代器语法映射到最少量的接口上。在下面的例子中,我们通过成员函数 asDerived()访问 CRTP 派生类:

Derived& asDerived() {
    return *static_cast<Derived*>(this);
}
Derived const& asDerived() const {
    return *static_cast<Derived const*>(this);
}

有了以上定义,facade 中大部分功能的实现就变得很直接了:

reference operator*() const {
    return asDerived().dereference();
}
Derived& operator++() {
    asDerived().increment();
    return asDerived();
}
Derived operator++(int) {
    Derived result(asDerived());
    asDerived().increment();
    return result;
}
friend bool operator== (IteratorFacade const& lhs, IteratorFacade const& rhs) {
    return lhs.asDerived().equals(rhs.asDerived());
}

定义链表迭代器

//链表节点定义:
template<typename T>
class ListNode {
public:
    T value;
    ListNode<T>* next = nullptr;
    ~ListNode() { delete next; }
};

通过使用 IteratorFacade,可以以一种很直接的方式定义指向这样一个链表的迭代器:

template<typename T>
class ListNodeIterator : public IteratorFacade<ListNodeIterator<T>, T, std::forward_iterator_tag> {
    ListNode<T>* current = nullptr;
public:
    T& dereference() const {
        return current->value;
    }
    void increment() {
        current = current->next;
    }
    bool equals(ListNodeIterator const& other) const {
        return current == other.current;
    }
    ListNodeIterator(ListNode<T>* current = nullptr) : current(current) { }
};

Mixin

考虑一个包含了一组点的简单 Polygon 类

class Point {
public:
    double x, y;
    Point() : x(0.0), y(0.0) { }
    Point(double x, double y) : x(x), y(y) { }
};
//扩展给每个 Point 关联一组信息(比如给点增加颜色,或者给每个点加个标签):
template<typename P>
class Polygon {
private:
    std::vector<P> points;
    public://public operations
};

用户可以通过继承创建与 Point 类似,但是包含了特定应用所需数据,并且提供了与 Point 相同的接口的类型:

class LabeledPoint : public Point {
public:
    std::string label;
    LabeledPoint() : Point(), label("") { }
    LabeledPoint(double x, double y) : Point(x, y), label("") {
    }
};

这一实现方式有其自身的缺点。如果给 Point 新增一个构造函数,就需要去更新所有的派生类

Mixins 是另一种可以客制化一个类型的行为但是不需要从其进行继承的方法

Mixins 反转了常规的继承方向,新的类型被作为基类“混合进”了继承层级中,而不是被创建为一个新的派生类

一个支持了 Mixins 的类模板通常会接受一组任意数量的 class,并从之进行派生:

template<typename… Mixins>
class Point : public Mixins... {
public:
    double x, y;
    Point() : Mixins()..., x(0.0), y(0.0) { } //Mixins()... 就是基类初始化包拓展
    Point(double x, double y) : Mixins()..., x(x), y(y) { }
};

现在就可以通过将一个包含了 label 的基类混合进来(mixin)来生成一个LabledPoint:

class Label {
public:
    std::string label;
    Label() : label("") { }
};

using LabeledPoint = Point<Label>;

甚至 mixin 几个基类:

class Color {
public:
    unsigned char red = 0, green = 0, blue = 0;
};

using MyPoint = Point<Label, Color>;

桥接 静态多态和动态多态

template<typename F>
void forUpTo (int n, F f) {
    for (int i = 0; i != n; ++i) {
        f(i);
    }
}
void printInt (int i) {
    std::cout << i << " ";
}
int main() {
    std::vector<int> values;
    // 从 0 到 4 插入值
    forUpTo(5, [&values](int i) {
                            values.push_back(i);
                        }
    );
    // 打印元素
    forUpTo(5, printInt); // prints 0 1 2 3 4
}

forUpTo()函数模板适用于所有的函数对象,包括 lambda,函数指针,以及任意实现了合适的 operator()运算符或者可以转换为一个函数指针或引用的类,而且每一次对forUpTo()的使用都很可能产生一个不同的函数模板实例。

一个缓解代码量增加的方式是将函数模板转变为非模板,如此就无需实例化。比如使用函数指针:

void forUpTo (int n, void (*f)(int)) {
    for (int i = 0; i != n; ++i) {
        f(i);
    }
}

虽然在给其传递printInt()的时候该方式可以正常工作,给其传递lambda却会导致错误

类模板 std::functional<> 则可以用来实现另一种类型的 forUpTo():

void forUpTo (int n, std::function<void(int)> f) {
    for (int i = 0; i != n; ++i)     {
        f(i)
    }
}

int main() {
	std::vector<int> v;
	forUpTo(9, [&v](int i){v.push_back(i);});
	forUpTo(9, print);
}

std::function<> 是一种高效的、广义形式的函数指针,但是不同的是前者还可以被用来存储lambda,以及其它任意实现了合适的operator()的函数对象

也就是说它可以存储任何类型的可调用对象,只要它们的调用签名相符。这是通过类型擦除技术实现的,即在运行时确定实际的可调用对象类型,而编译时则对用户隐藏这些细节。

// 事件处理器类
class EventHandler {
public:
	// 使用 std::function 来存储任意类型的可调用对象
	// 这里假定所有事件处理函数都没有参数并返回 void
	void registerCallback(const std::function<void()>& callback) {
		callbacks.push_back(callback);
	}
	// 触发所有已注册的事件处理函数
	void triggerEvents() {
		for (auto& callback : callbacks) {
			callback();  // 调用存储的函数
		}
	}
private:
	std::vector<std::function<void()>> callbacks;
};

int main() {
	EventHandler handler;
	
	// 注册一个 lambda 表达式
	handler.registerCallback([](){ std::cout << "Lambda" << std::endl; });
	
	// 注册另一个lambda
	handler.registerCallback([](){ std::cout << "another Lambda" << std::endl; });
	
	// 假设有一个成员函数需要被调用
	struct MyClass {
		void memberFunction() {
			std::cout << "member function" << std::endl;
		}
	};
	
	MyClass myObject; //实例化成员函数的那个类
	// 注册一个类成员函数
	handler.registerCallback([&myObject](){	myObject.memberFunction(); });
	
	// 触发所有事件
	handler.triggerEvents();
}

元编程

元编程的特性之一是在编译期就可以进行一部分用户定义的计算

值元编程

一个在编译期计算求和的函数可以写成这样:

template <typename T>
constexpr T sum(T num) {
	if (num == 1) return 1;
	return num + sum(num - 1);
}
int main() {
	std::cout << sum(100) << std::endl;
}

可以用array做验证:std::array<int, sum(100)> a;

类型元编程

//主模板
template<typename T>
struct RemoveAllExtentsT {
    using Type = T;
};
//偏特化,with bounds
template<typename T, std::size_t SZ>
struct RemoveAllExtentsT<T[SZ]> {
    using Type = typename RemoveAllExtentsT<T>::Type;
};
//偏特化,without bounds
template<typename T>
struct RemoveAllExtentsT<T[]> {
    using Type = typename RemoveAllExtentsT<T>::Type;
};
//别名模板
template<typename T>
using RemoveAllExtents = typename RemoveAllExtentsT<T>::Type;

RemoveAllExtents 是一种类型元函数,它会从一个类型中移除掉任意数量的顶层“数组层”

RemoveAllExtents<int[]> // yields int
RemoveAllExtents<int[5][10]> // yields int
RemoveAllExtents<int[][10]> // yields int
RemoveAllExtents<int(*)[5]> // yields int(*)[5]

混合元编程

混合元编程:在编译期间组合一些有运行期效果的代码

通过一个简单的例子来说明这一原理:计算两个 std::array 的点乘结果。

首先我们知道 std::array 是具有固定长度的容器模板,假设有两个类型相同的 sta::array 对象,

其点乘结果可以通过如下方式计算:

template<typename T, std::size_t N>
auto dotProduct(std::array<T, N> const& x, std::array<T, N> const& y) {
    T result{};
    for (std::size_t k = 0; k<N; ++k) {
        result += x[k]*y[k];
    }
    return result;
}

如果对 for 循环进行直接编译的话,那么就会生成分支指令,幸运的是,IDE会做优化,所以不如实现一版不需要循环的:

template <typename T, std::size_t N>
struct DotProductT {
	static inline T result(T* a, T* b) {
		return *a * *b + DotProductT<T, N-1>::result(a+1, b+1);
	}
};
//偏特化,终止递归
template<typename T>
struct DotProductT<T, 0> {
	static inline T result(T*, T*) {
		return T{};
	}
};

template <typename T, std::size_t N>
auto dotProduct(std::array<T, N> const& x, std::array<T, N> const& y){
	return DotProductT<T, N>::result(x.begin(), y.begin());
}

解释:为什么上面要用到类模板,因为可以做偏特化,而函数模板不支持偏特化

另一个例子:让数值计算发生在运行期,单位计算发生在编译期。具体来说,用一个基于主单位的分数来记录相关单位。比如如果时间的主单位是秒,那么就用 1/1000 就是微秒,用 60/1 就是分。因此关键就是要定义一个比例类型,使得每一个数值都有其自己的类型

//先定义一个比例类型
template <unsigned N, unsigned D = 1>
struct Ratio {
	static constexpr unsigned num = N; //分子
	static constexpr unsigned den = D; //分母
	//using Type = Ratio<num, den>; //因为没用ctor,所以要组合。其实也不需要
};
//现在就可以定义在编译期对两个单位进行求和之类的运算
template <typename R1, typename R2>
struct RatioAddImpl {
private:
	//从下面就可以看出来要使用static类型
	static constexpr unsigned den = R1::den * R2::den;
	static constexpr unsigned num = R1::num * R2::den + R2::num * R1::den;
public:
	using Type = Ratio<num, den>; //将分子分母计算好后塞进Ratio
};
//便于使用
template <typename R1, typename R2>
using RatioAdd = typename RatioAddImpl<R1, R2>::Type;
//可以在编译期间计算两个比率之和啦
int main() {
	using R1 = Ratio<1, 1000>;
	using R2 = Ratio<2, 3>;
	using RS = RatioAdd<R1, R2>;
	std::cout << RS::num << " " << RS::den << std::endl; //2003 3000
}

递归实例化的代价

实例化模板的成本并不低廉,即使是比较适中的类模板,其实例依然有可能占用数KB的内存,而且这部分被占用的内存在编译完成之前不可以被回收利用。

举个例子,sqrt计算平方根,如何采用普通递归(偏特化终结递归)的方式,也会实例化出很多实例。

幸运的是,有一些技术可以被用来降低实例化的数目,就是使用 IfThenElse,没用到的部分不会实例化,由此可以降低一半的实例化次数

template <bool condition, typename TrueType, typename FalseType>
struct IfThenElseT {
	using Type = TrueType;
};
//偏特化,处理主模板的condition为false的情况
template <typename TrueType, typename FalseType>
struct IfThenElseT<false, TrueType, FalseType> {
	using Type = FalseType;
};
//别名模板
template <bool COND, typename TrueType, typename FalseType>
using IfThenElse = typename IfThenElseT<COND, TrueType, FalseType>::Type;

// 主递归步骤的主模板
template<int N, int LO=1, int HI=N>
struct Sqrt {
	// 计算中点,四舍五入
	static constexpr auto mid = (LO+HI+1)/2;
	// 在减半区间内寻找一个不太大的值
	using SubT = IfThenElse<(N<mid*mid),Sqrt<N,LO,mid-1>,Sqrt<N,mid,HI>>;
	static constexpr auto value = SubT::value;
};
// 递归结束标准的部分特殊化
template<int N, int S>
struct Sqrt<N, S, S> {
	static constexpr auto value = S;
};

int main() {
	std::cout << Sqrt<16>::value << std::endl;
}

记住,为一个类模板的实例定义类型别名,不会导致 C++编译器去实例化该实例,所以上述代码中,Sqrt<N,LO,mid-1> 和 Sqrt<N,mid,HI>,二者不会全部同时实例化。

和之前的方法相比,这会让实例化的数量和 lbN(以2为底,binary) 成正比:当 N 比较大的时候,这会大大降低元程序实例化的成本。

Typelists 类型列表

类型列表通常是按照类模板特例的形式实现的,它将自身的内容编码到了参数包中。一种将其内容编码到参数包中的类型列表的直接实现方式如下:

template<typename... Elements>
class Typelist { };

一个空的类型列表被写为Typelist<>,一个只包含int的类型列表被写为 Typelist。下面是一个包含了所有有符号整型的类型列表:

using SignedIntegralTypes = Typelist<signed char, short, int, long, long long>;

想要操作这个Typelist就需要拆分,通常做法是将第一个元素从剩余元素中分离。比如 Front 元函数会从类型列表中提取第一个元素:

template <typename... Elements>
struct Typelist {};

//主模板
template <typename T>
class FrontT {};
//偏特化
template <typename Head, typename... Tail>
class FrontT<Typelist<Head, Tail...>> {
public:
	using Type = Head;
};
//别名模板
template <typename List>
using Front = typename FrontT<List>::Type;

如此一来,FrontT<SignedIntegralTypes>::Type 返回的就是signed char。

同样实现一下PopFront 元函数,它会删除类型列表中的第一个元素。

如何实现?在实现上它会将类型列表中的元素分为头尾两部分,然后用尾部的元素创建一个新的 Typelist 特例

https://www.canva.com/ ccc123@126.com https://www.canva.cn/design/DAGCY9ZvQrA/7U4VXYhySbw5vC-oLSfj5w/edit

web.sydney-ai.com tuzi/tuzituzi maomi/maomimaomi pytpor/8888iiii

益丰 https://api.ephone.chat/ pobo/8888iiii

About

cpp_template_tips

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors