C++ 模板沉思录(下)

Python猫

共 1783字,需浏览 4分钟

 · 2020-08-04

Python猫” ,一个值得加星标的公众号
花下猫语:在我们读者群里,最近出现了比较多关于 C++ 的讨论,还兴起了一股学习 C++ 的风气。樱雨楼小姐姐对 C++ 的模板深有研究,系统地梳理成了一篇近 4 万字的文章!本文是下篇,分享给大家~

樱雨楼 | 原创作者

豌豆花下猫 | 编辑

内容接-> C++ 模板沉思录(上)

5 神奇的“多功能”函数——编译期分派

本章旨在讨论这样的一个问题:

如何实现一个“多功能函数”,使得单一的“多功能函数”在面对不同类型的参数时,能够自动选择针对当前类型的最佳方案或独特功能?

本章亦将通过一小一大两个案例,来讨论这一问题。

5.1 迭代器的advance函数

STL中的advance函数,可用于将迭代器向前(后)推进N步。显然,不同的迭代器类别对前进这一动作的支持是不一样的:如前向迭代器,只能前进,不能后退;双向迭代器,可双向移动;而随机访问迭代器,不仅可以双向移动,还可以“大跨步地”移动。为了讨论方便,我们首先用数字的加减模拟出这三种不同类别的“迭代器”。请看以下示例:

// 模拟的“前向迭代器”
template <typename T>
class ForwardIterator
{

public:

    // 构造函数
    ForwardIterator(const T &val);


    // operator*
    T &operator*() { return __val; }


    // 只能++
    void operator++(int) { __val++; }


private:

    // 数据成员
    T __val;
};


// 模拟的“双向迭代器”
template <typename T>
class BidirectionalIterator
{

public:

    // 构造函数
    BidirectionalIterator(const T &val);


    // operator*
    T &operator*() { return __val; }


    // 可以++和--
    void operator++(int) { __val++; }
    void operator--(int) { __val--; }


private:

    // 数据成员
    T __val;
};


// 模拟的“随机访问迭代器”
template <typename T>
class RandomAccessIterator
{

public:

    // 构造函数
    RandomAccessIterator(const T &val);


    // operator*
    T &operator*() { return __val; }


    // 不仅可以++和--,还可以直接+=、-=
    void operator++(int) { __val++; }
    void operator--(int) { __val--; }
    void operator+=(int N) { __val += N; }
    void operator-=(int N) { __val -= N; }


private:

    // 数据成员
    T __val;
};


template <typename T>
ForwardIterator::ForwardIterator(const T &val): __val(val) {}


template <typename T>
BidirectionalIterator::BidirectionalIterator(const T &val): __val(val) {}


template <typename T>
RandomAccessIterator::RandomAccessIterator(const T &val): __val(val) {}

此时,我们就可以实现出一个简单的advance函数了。请看以下示例:

template <typename Iterator>
void Advance(Iterator &iter, int N)
{
    for (int _ = 0; _ < N; _++) iter++;
}


int main()
{
    ForwardIterator<int> forwardIterator(0);
    BidirectionalIterator<int> bidirectionalIterator(0);
    RandomAccessIterator<int> randomAccessIterator(0);

    Advance(forwardIterator, 10);
    Advance(bidirectionalIterator, 10);
    Advance(randomAccessIterator, 10);

    cout << *forwardIterator << endl;        // 10
    cout << *bidirectionalIterator << endl;  // 10
    cout << *randomAccessIterator << endl;   // 10
}

上述实现似乎运行正常,但稍加分析不难发现,这一实现具有以下两个主要缺点:

  1. N必须为正数,即使对于(允许N为负数的)双向迭代器和随机访问迭代器而言也是一样
  2. 随机访问迭代器并不需要像此实现这样“步进”,从而造成了效率的浪费

怎么样才能既提供单一接口,又能够让接口有能力分辨出不同的迭代器类别呢?首先,我们需要将“迭代器的类别”这一语义表达出来,可以通过“强行编造”一些空类实现这一语义。请看以下示例:

// “强行编造”的三种不同的迭代器类别
struct ForwardIteratorTag {};
struct BidirectionalIteratorTag {};
struct RandomAccessIteratorTag {};

然后,我们可以为每个迭代器添加一个typedef,从而表示出这个迭代器的类别。请看以下示例:

template <typename T>
class ForwardIterator
{

public:

    // 前向迭代器的类别是ForwardIteratorTag
    typedef ForwardIteratorTag IteratorCategory;


    // ...
};


template <typename T>
class BidirectionalIterator
{

public:

    // 双向迭代器的类别是BidirectionalIteratorTag
    typedef BidirectionalIteratorTag IteratorCategory;


    // ...
};


template <typename T>
class RandomAccessIterator
{

public:

    // 随机访问迭代器的类别是RandomAccessIteratorTag
    typedef RandomAccessIteratorTag IteratorCategory;


    // ...
};

此时,当我们拿到一个迭代器类型Iterator时,我们就可以通过typename Iterator::IteratorCategory来获取到当前迭代器的类别了。

同时,我们可以以迭代器类别作为Advance函数的第三参数,从而重载出多个不同版本的Advance函数。请看以下示例:

// 适用于前向迭代器的版本
template <typename Iterator>
void __Advance(Iterator &iter, int N, ForwardIteratorTag)
{
    for (int _ = 0; _ < N; _++) iter++;
}


// 适用于双向迭代器的版本
template <typename Iterator>
void __Advance(Iterator &iter, int N, BidirectionalIteratorTag)
{
    if (N > 0)
    {
        for (int _ = 0; _ < N; _++) iter++;
    }
    else
    {
        for (int _ = 0; _ < -N; _++) iter--;
    }
}


// 适用于随机访问迭代器的版本
template <typename Iterator>
void __Advance(Iterator &iter, int N, RandomAccessIteratorTag)
{
    iter += N;
}

此时,我们已经拥有了两组工具:

  1. 可以通过typename Iterator::IteratorCategory来获取到当前迭代器的类别
  2. 实现了3个__Advance函数,分别适用于三个迭代器类别

此时,只要我们使用迭代器类别(作为第三参数)去调用__Advance函数,编译器就将根据重载确定规则,选择适用于当前迭代器类别的__Advance函数进行调用了。请看以下示例:

int main()
{
    ForwardIterator<int> forwardIterator(0);
    BidirectionalIterator<int> bidirectionalIterator(0);
    RandomAccessIterator<int> randomAccessIterator(0);

    // 重载确定至__Advance(Iterator &iter, int N, ForwardIteratorTag)版本
    __Advance(forwardIterator, 10typename ForwardIterator<int>::IteratorCategory());

    // 重载确定至__Advance(Iterator &iter, int N, BidirectionalIteratorTag)版本
    __Advance(bidirectionalIterator, 10typename BidirectionalIterator<int>::IteratorCategory());

    // 重载确定至__Advance(Iterator &iter, int N, RandomAccessIteratorTag)版本
    __Advance(randomAccessIterator, 10typename RandomAccessIterator<int>::IteratorCategory());

    cout << *forwardIterator << endl;        // 10
    cout << *bidirectionalIterator << endl;  // 10
    cout << *randomAccessIterator << endl;   // 10
}

这就结束了吗?怎么感觉函数的调用这么麻烦呢?显然还未结束。

不难发现,在任何一个函数的实现代码中,我们都不仅拥有参数值,还拥有每个参数的类型。所以,我们可以再实现一个只有两个参数的函数,并在其内部构造出“typename Iterator::IteratorCategory()”,并调用三个参数的__Advance函数。而这,就是基于重载确定的编译期分派技术。请看以下示例:

// 最终的“多功能函数”
template <typename Iterator>
void Advance(Iterator &iter, int N)
{
    // 根据typename Iterator::IteratorCategory()进行基于重载确定的编译期分派
    __Advance(iter, N, typename Iterator::IteratorCategory());
}


int main()
{
    ForwardIterator<int> forwardIterator(0);
    BidirectionalIterator<int> bidirectionalIterator(0);
    RandomAccessIterator<int> randomAccessIterator(0);

    // 重载确定至__Advance(Iterator &iter, int N, ForwardIteratorTag)版本
    Advance(forwardIterator, 10);

    // 重载确定至__Advance(Iterator &iter, int N, BidirectionalIteratorTag)版本
    Advance(bidirectionalIterator, 10);

    // 重载确定至__Advance(Iterator &iter, int N, RandomAccessIteratorTag)版本
    Advance(randomAccessIterator, 10);

    cout << *forwardIterator << endl;        // 10
    cout << *bidirectionalIterator << endl;  // 10
    cout << *randomAccessIterator << endl;   // 10
}

至此,我们就完成了Advance这个“多功能函数”的实现。但最后,我们还有一个重要问题需要解决:指针也是迭代器,那么指针的迭代器类型(当然是随机访问迭代器)怎么获取?

也许不用我说,你就已经知道答案了,解决方案就是“加中间层可解决一切问题”定理。我们可以为“获取迭代器类型”这一操作添加一个中间层,并在此中间层中,对指针类型进行特化。请看以下示例:

// 迭代器的迭代器类型
template <typename Iterator>
struct IteratorTraits
{

    typedef typename Iterator::IteratorCategory IteratorCategory;
};


// 指针的迭代器类型
template <typename T>
struct IteratorTraits
{

    typedef RandomAccessIteratorTag IteratorCategory;
};

同时,我们还需要将上面的Advance函数中的“简单粗暴的typename Iterator::IteratorCategory”替换为我们刚刚实现的IteratorTraits方法:

template <typename Iterator>
void Advance(Iterator &iter, int N)
{
    // 根据typename IteratorTraits::IteratorCategory()进行基于重载确定的编译期分派
    __Advance(iter, N, typename IteratorTraits::IteratorCategory());
}

至此,Advance函数的实现也就全部完成了。

接下来,我们以一个更为复杂,也更为神奇的功能,继续讨论编译期分派这一技术。

5.2 “万能的”打印函数

本节将要实现的功能,其最终使用起来十分简单:

print(X);  // "X"可以是任何值!

没错,这是一个可以打印任何值的函数!通过上一节的铺垫,我们知道:作为实现者,我们不仅可以得到X的值,还能“顺便”得到X的类型。所以,我们就可以在X的类型上大做文章,针对不同的类型实现出不同的打印函数,最后,通过这个print函数进行编译期分派,从而实现出这一神奇的函数。

首先应该实现什么呢?不难发现,X可以是一个“可以直接cout的值”、(支持迭代器的)容器、Pair、Tuple、Stack、Queue等类别。所以,我们首先需要对X的这些不同的类别进行分类,通过创建很多空的Tag类即可实现此功能。请看以下示例:

// 默认类别
struct __CommonTag {};

// 线性容器类别
struct __SequenceContainerTag {};

// Map容器类别
struct __MapContainerTag {};

// Set容器类别
struct __SetContainerTag {};

// Pair类别
struct __PairTag {};

// Map中的Pair类别
struct __MapPairTag {};

// Tuple类别
struct __TupleTag {};

// Stack类别
struct __StackTag {};

// Queue类别
struct __QueueTag {};

然后,通过建立一系列的Traits,我们就可以将X的类型映射到这些Tag上:

// 如果T不是下面所提及的类型中的任何一种,那么T的类别就是__CommonTag(即:“可以直接cout的类型”)
template <typename T>
struct __CategoryTraits
{

    typedef __CommonTag __Category;
};


// array的类别是__SequenceContainerTag
template <typename T, size_t N>
struct __CategoryTraits>
{

    typedef __SequenceContainerTag __Category;
};


// deque的类别是__SequenceContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SequenceContainerTag __Category;
};


// forward_list的类别是__SequenceContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SequenceContainerTag __Category;
};


// list的类别是__SequenceContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SequenceContainerTag __Category;
};


// vector的类别是__SequenceContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SequenceContainerTag __Category;
};


// map的类别是__MapContainerTag
template <typename K, typename V>
struct __CategoryTraits>
{

    typedef __MapContainerTag __Category;
};


// multimap的类别是__MapContainerTag
template <typename K, typename V>
struct __CategoryTraits>
{

    typedef __MapContainerTag __Category;
};


// unordered_map的类别是__MapContainerTag
template <typename K, typename V>
struct __CategoryTraits>
{

    typedef __MapContainerTag __Category;
};


// unordered_multimap的类别是__MapContainerTag
template <typename K, typename V>
struct __CategoryTraits>
{

    typedef __MapContainerTag __Category;
};


// set的类别是__SetContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SetContainerTag __Category;
};


// multiset的类别是__SetContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SetContainerTag __Category;
};


// unordered_set的类别是__SetContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SetContainerTag __Category;
};


// unordered_multiset的类别是__SetContainerTag
template <typename T>
struct __CategoryTraits>
{

    typedef __SetContainerTag __Category;
};


// pair的类别是__PairTag
template <typename T1, typename T2>
struct __CategoryTraits>
{

    typedef __PairTag __Category;
};


// tuple的类别是__TupleTag
template <typename... Types>
struct __CategoryTraits>
{

    typedef __TupleTag __Category;
};


// stack的类别是__StackTag
template <typename T>
struct __CategoryTraits>
{

    typedef __StackTag __Category;
};


// queue的类别是__QueueTag
template <typename T>
struct __CategoryTraits>
{

    typedef __QueueTag __Category;
};


// priority_queue的类别是__StackTag
template <typename T>
struct __CategoryTraits>
{

    typedef __StackTag __Category;
};

接下来需要解决的问题是:我们希望面对类似于一维数组这样的“较小的值”和类似于高维数组这样的“较大的值”时,能够采用或紧凑,或分散的不同的打印格式。具体什么是“较小的值”呢?这里,我们认为:如果X本身的类别就是__CommonTag,或X中的值(前提是X本身的类别不是__CommonTag)的类别是__CommonTag,则认为X是一个“较小的值”,采取紧凑的打印格式,否则,就认为X是一个“较大的值”,采取分散的打印格式。

那么,我们就又引出了一个问题:对于某些类型,如Pair和Tuple,其中的各个元素的类型是不一样的,即:各个元素的类别也是不一样的;同时,很显然,当我们面对多个类别时,只要其中有一个类别不是__CommonTag,那么我们就应当认为这些类别的“最强大类别”不是__CommonTag。因此,我们首先需要实现一个获取“最强大类别”的Traits。请看以下示例:

// 原型
// 通过typename __CategoryPromotionTraits::__Category获取“最强大类别”
template <typename... Tags>
struct __CategoryPromotionTraits;


// 如果有两个任意的类别,则随便选一个类别作为“更强大类别”即可...
template <typename Tag1, typename Tag2>
struct __CategoryPromotionTraits
{

    typedef Tag1 __Category;
};


// ...但是,如果右类别是__CommonTag,则“更强大类别”需要“敬而远之”...
template <typename Tag1>
struct __CategoryPromotionTraits
{

    typedef Tag1 __Category;
};


// ...同理,如果左类别是__CommonTag,则“更强大类别”也需要“敬而远之”...
template <typename Tag2>
struct __CategoryPromotionTraits<__CommonTag, Tag2>
{

    typedef Tag2 __Category;
};


// ...只有当“你我都是普通人”时,“更强大类别”才是__CommonTag
template <>
struct __CategoryPromotionTraits<__CommonTag, __CommonTag>
{

    typedef __CommonTag __Category;
};


// 面对不止两个类别时,“最强大类别”应该是Tag1与Tags...的“最强大类别”之间的“更强大类别”
template <typename Tag1, typename... Tags>
struct __CategoryPromotionTraits
{

    typedef typename __CategoryPromotionTraits<
        Tag1,
        typename __CategoryPromotionTraits::__Category
    >::__Category __Category;
};

接下来,我们就来实现能够获取X中的元素的类别(即X的“子类别”)的Traits:

// 原型
// 通过typename __SubCategoryTraits::__Category获取X中的元素的类别
template <typename Tag, typename T>
struct __SubCategoryTraits;


// __CommonTag没有子类别
template <typename T>
struct __SubCategoryTraits<__CommonTag, T>
{

    typedef void __Category;
};


// __SequenceContainerTag的子类别就是T::value_type的类别
template <typename T>
struct __SubCategoryTraits<__SequenceContainerTag, T>
{

    typedef typename __CategoryTraits<typename T::value_type>::__Category __Category;
};


// __MapContainerTag的子类别一定是__MapPairTag
template <typename T>
struct __SubCategoryTraits<__MapContainerTag, T>
{

    typedef __MapPairTag __Category;
};


// __SetContainerTag的子类别就是T::value_type的类别
template <typename T>
struct __SubCategoryTraits<__SetContainerTag, T>
{

    typedef typename __CategoryTraits<typename T::value_type>::__Category __Category;
};


// __MapPairTag的子类别是T::first_type的类别与T::second_type的类别之间的“更强大类别”
template <typename T>
struct __SubCategoryTraits<__MapPairTag, T>
{

    typedef typename __CategoryPromotionTraits<
        typename __CategoryTraits<typename T::first_type>::__Category,
        typename __CategoryTraits<typename T::second_type>::__Category
    >::__Category __Category;
};


// 和__MapPairTag一样
// __PairTag的子类别也是T::first_type的类别与T::second_type的类别之间的“更强大类别”
template <typename T>
struct __SubCategoryTraits<__PairTag, T>
{

    typedef typename __CategoryPromotionTraits<
        typename __CategoryTraits<typename T::first_type>::__Category,
        typename __CategoryTraits<typename T::second_type>::__Category
    >::__Category __Category;
};


// __TupleTag的子类别是各个Types的类别之中的“最强大类别”
template <typename... Types>
struct __SubCategoryTraits<__TupleTag, tuple>
{

    typedef typename __CategoryPromotionTraits<
        typename __CategoryTraits::__Category...
    >::__Category __Category;
};


// __StackTag的子类别就是T::value_type的类别
template <typename T>
struct __SubCategoryTraits<__StackTag, T>
{

    typedef typename __CategoryTraits<typename T::value_type>::__Category __Category;
};


// __QueueTag的子类别就是T::value_type的类别
template <typename T>
struct __SubCategoryTraits<__QueueTag, T>
{

    typedef typename __CategoryTraits<typename T::value_type>::__Category __Category;
};

有了__CategoryTraits和__SubCategoryTraits这两个工具,我们的准备工作也就基本上完成了。接下来是最后的一些简单的准备工作:

  1. 我们需要定义一些控制打印样式的字符或字符串常量。请看以下示例:
// 缩进空白字符
const char   __SPACE                    = ' ';

// 单次缩进长度
const int    __INDENTATION_LEN          = 4;

// 元素之间的分隔符
const string __VALUE_SPLICE             = ", ";

// 行末分隔符
const char   __LINE_END                 = ',';

// 线性容器类别的左右定界符
const char   __SEQUENCE_CONTAINER_BEGIN = '[';
const char   __SEQUENCE_CONTAINER_END   = ']';

// Map容器类别的左右定界符
const char   __MAP_CONTAINER_BEGIN      = '{';
const char   __MAP_CONTAINER_END        = '}';

// Set容器类别的左右定界符
const char   __SET_CONTAINER_BEGIN      = '{';
const char   __SET_CONTAINER_END        = '}';

// Pair类别的左右定界符
const char   __PAIR_BEGIN               = '(';
const char   __PAIR_END                 = ')';

// Map中的Pair类别的左右定界符
const char   __MAP_PAIR_BEGIN           = '(';
const char   __MAP_PAIR_END             = ')';

// Map中的Pair类别的键值对分隔符
const string __MAP_PAIR_SPLICE          = ": ";

// Map中的Pair类别的键值对行末分隔符
const char   __MAP_PAIR_LINE_END        = ':';

// Tuple容器类别的左右定界符
const char   __TUPLE_BEGIN              = '(';
const char   __TUPLE_END                = ')';

// Stack容器类别的左右定界符
const char   __STACK_BEGIN              = '[';
const char   __STACK_END                = ']';

// Queue容器类别的左右定界符
const char   __QUEUE_BEGIN              = '[';
const char   __QUEUE_END                = ']';
  1. 对于Stack,如果我们将1,2,3依次入栈,则取出时只能按3,2,1的顺序出栈,这将造成很奇怪的打印效果。所以我们需要一个简单的函数,用于将Stack反序。请看以下示例:
template <typename T>
T __reverseStack(T oriStack)  // 必须使用值传递
{
    T reverseStack;

    while (!oriStack.empty())
    {
        reverseStack.push(oriStack.top());
        oriStack.pop();
    }

    return reverseStack;
}

终于可以开始着手实现最重要的print函数了!这里我们通过实现很多个__PrintData类的特化来实现针对不同类别及其子类别组合的编译期分派。那么,__PrintData类需要哪些模板参数呢?X的类别、X的子类别、X的类型,显然都是需要的,此外,我们还需要在模板参数中维护一个整数,用于使每个模板都能够知道“我在第几层?”,以实现打印时的缩进控制。请看以下示例:

// 原型
// 通过__PrintData::__Print(val)打印val
template <typename SelfTag, typename SubTag, typename T, int N>
struct __PrintData;


// 打印__CommonTag类别的值(__CommonTag的子类别一定是void),采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__CommonTag, void, T, N>
{

    static void __Print(const T &val);
};


// 打印__SequenceContainerTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__SequenceContainerTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__SequenceContainerTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__MapContainerTag类别 + __MapPairTag子类别组合的值,采用宽松的打印格式
template <typename T, int N>
struct __PrintData<__MapContainerTag, __MapPairTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__SetContainerTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__SetContainerTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__SetContainerTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__PairTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__PairTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__PairTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__MapPairTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__MapPairTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__MapPairTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__TupleTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__TupleTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__TupleTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__StackTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__StackTag, SubTag, T, N>
{

    static void __Print(const T &val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__StackTag, __CommonTag, T, N>
{

    static void __Print(const T &val);
};


// 打印__QueueTag + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
struct __PrintData<__QueueTag, SubTag, T, N>
{

    static void __Print(T val);
};


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
struct __PrintData<__QueueTag, __CommonTag, T, N>
{

    static void __Print(T val);
};

以下是除了Tuple以外的(Tuple的打印函数的实现我们将在稍后讨论)各个打印函数的实现:

// 打印__CommonTag类别的值(__CommonTag的子类别一定是void),采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__CommonTag, void, T, N>::__Print(const T &val)
{
    // 直接打印缩进与val
    cout << string(N * __INDENTATION_LEN, __SPACE) << val;
}


// 打印__SequenceContainerTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__SequenceContainerTag, SubTag, T, N>::__Print(const T &val)
{
    // 打印缩进与线性容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_BEGIN << endl;

    for (auto &subVal: val)
    {
        // 缩进层数+1,使用线性容器中的元素的__PrintData版本,依次打印线性容器中的每个元素
        __PrintData<
            SubTag,
            typename __SubCategoryTraitstypename T::value_type>::__Category,
            typename T::value_type,
            N + 1
        >::__Print(subVal);

        // 打印行末分隔符后换行
        cout << __LINE_END << endl;
    }

    // 打印缩进与线性容器类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__SequenceContainerTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 打印缩进与线性容器类别的左定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SEQUENCE_CONTAINER_BEGIN;

    // 不换行,依次打印线性容器中的每个元素,以及元素之间的分隔符
    if (!val.empty())
    {
        cout << val.front();

        for (auto iter = next(val.begin()); iter != val.end(); iter++)
        {
            cout << __VALUE_SPLICE << *iter;
        }
    }

    // 打印线性容器类别的右定界符
    cout << __SEQUENCE_CONTAINER_END;
}


// 打印__MapContainerTag类别 + __MapPairTag子类别组合的值,采用宽松的打印格式
template <typename T, int N>
void __PrintData<__MapContainerTag, __MapPairTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Map容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_CONTAINER_BEGIN << endl;

    for (auto &subVal: val)
    {
        // 缩进层数+1,直接使用__MapPairTag类别以及Map容器的子类别生成__PrintData,用于打印Map中的Pair
        __PrintData<
            __MapPairTag,
            typename __SubCategoryTraits<__MapPairTag, typename T::value_type>::__Category,
            typename T::value_type,
            N + 1
        >::__Print(subVal);

        // 打印行末分隔符后换行
        cout << __LINE_END << endl;
    }

    // 打印缩进与Map容器类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_CONTAINER_END;
}


// 打印__SetContainerTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__SetContainerTag, SubTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Set容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_BEGIN << endl;

    for (auto &subVal: val)
    {
        // 缩进层数+1,使用Set容器中的元素的__PrintData版本,依次打印Set容器中的每个元素
        __PrintData<
            SubTag,
            typename __SubCategoryTraitstypename T::value_type>::__Category,
            typename T::value_type,
            N + 1
        >::__Print(subVal);

        // 打印行末分隔符后换行
        cout << __LINE_END << endl;
    }

    // 打印缩进与Set容器类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__SetContainerTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Set容器类别的左定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __SET_CONTAINER_BEGIN;

    // 不换行,依次打印Set容器中的每个元素,以及元素之间的分隔符
    if (!val.empty())
    {
        cout << *val.begin();

        for (auto iter = next(val.begin()); iter != val.end(); iter++)
        {
            cout << __VALUE_SPLICE << *iter;
        }
    }

    // 打印Set容器类别的右定界符
    cout << __SET_CONTAINER_END;
}


// 打印__PairTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__PairTag, SubTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Pair类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_BEGIN << endl;

    // 缩进层数+1,使用val.first的__PrintData版本打印val.first
    __PrintData<

        typename __CategoryTraits<typename T::first_type>::__Category,

        typename __SubCategoryTraits<
            typename __CategoryTraits<typename T::first_type>::__Category,
            typename T::first_type
        >::__Category,

        typename T::first_type,

        N + 1

    >::__Print(val.first);

    // 打印行末分隔符后换行
    cout << __LINE_END << endl;

    // 缩进层数+1,使用val.second的__PrintData版本打印val.second
    __PrintData<

        typename __CategoryTraits<typename T::second_type>::__Category,

        typename __SubCategoryTraits<
            typename __CategoryTraits<typename T::second_type>::__Category,
            typename T::second_type
        >::__Category,

        typename T::second_type,

        N + 1

    >::__Print(val.second);

    // 打印行末分隔符后换行,再打印缩进与Pair类别的右定界符
    cout << __LINE_END << endl << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__PairTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 直接依次打印缩进、Pair类别的左定界符、val.first、元素之间的分隔符、
    // val.second以及Pair类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __PAIR_BEGIN <<
        val.first << __VALUE_SPLICE << val.second << __PAIR_END;
}


// 打印__MapPairTag类别 + 任意子类别组合的值,采用宽松的打印格式...
// 此版本的实现与__PrintData<__PairTag, SubTag, T, N>版本高度相似
// 唯一的区别在于定界符的选取不同
template <typename SubTag, typename T, int N>
void __PrintData<__MapPairTag, SubTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Map中的Pair类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_BEGIN << endl;

    // 缩进层数+1,使用val.first的__PrintData版本打印val.first
    __PrintData<

        typename __CategoryTraits<typename T::first_type>::__Category,

        typename __SubCategoryTraits<
            typename __CategoryTraits<typename T::first_type>::__Category,
            typename T::first_type
        >::__Category,

        typename T::first_type,

        N + 1

    >::__Print(val.first);

    // 打印Map中的Pair类别的键值对行末分隔符后换行
    cout << __MAP_PAIR_LINE_END << endl;

    // 缩进层数+1,使用val.second的__PrintData版本打印val.second
    __PrintData<

        typename __CategoryTraits<typename T::second_type>::__Category,

        typename __SubCategoryTraits<
            typename __CategoryTraits<typename T::second_type>::__Category,
            typename T::second_type
        >::__Category,

        typename T::second_type,

        N + 1

    >::__Print(val.second);

    // 打印行末分隔符后换行,再打印缩进与Map中的Pair类别的右定界符
    cout << __LINE_END << endl << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
// 此版本的实现与__PrintData<__PairTag, __CommonTag, T, N>版本高度相似
// 唯一的区别在于定界符的选取不同
template <typename T, int N>
void __PrintData<__MapPairTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 直接依次打印缩进、Map中的Pair类别的左定界符、val.first、
    // Map中的Pair类别的键值对分隔符、val.second以及Map中的Pair类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __MAP_PAIR_BEGIN <<
        val.first << __MAP_PAIR_SPLICE << val.second << __MAP_PAIR_END;
}


// 打印__StackTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__StackTag, SubTag, T, N>::__Print(const T &val)
{
    // 得到一个反序的Stack
    T reverseVal = __reverseStack(val);

    // 打印缩进与Stack容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_BEGIN << endl;

    while (!reverseVal.empty())
    {
        // 缩进层数+1,使用Stack容器中的元素的__PrintData版本,依次打印Stack容器中的每个元素
        __PrintData<
            SubTag,
            typename __SubCategoryTraitstypename T::value_type>::__Category,
            typename T::value_type,
            N + 1
        >::__Print(reverseVal.top());

        // 打印行末分隔符后换行
        cout << __LINE_END << endl;

        // 出栈
        reverseVal.pop();
    }

    // 打印缩进与Stack容器类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__StackTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 得到一个反序的Stack
    T reverseVal = __reverseStack(val);

    // 打印缩进与Stack容器类别的左定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __STACK_BEGIN;

    // 不换行,依次打印Stack容器中的每个元素,以及元素之间的分隔符
    if (!reverseVal.empty())
    {
        cout << reverseVal.top();
        reverseVal.pop();

        while (!reverseVal.empty())
        {
            cout << __VALUE_SPLICE << reverseVal.top();
            reverseVal.pop();
        }
    }

    // 打印Stack容器类别的右定界符
    cout << __STACK_END;
}


// 打印__QueueTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__QueueTag, SubTag, T, N>::__Print(T val)
{
    // 打印缩进与Queue容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_BEGIN << endl;

    while (!val.empty())
    {
        // 缩进层数+1,使用Queue容器中的元素的__PrintData版本,依次打印Queue容器中的每个元素
        __PrintData<
            SubTag,
            typename __SubCategoryTraitstypename T::value_type>::__Category,
            typename T::value_type,
            N + 1
        >::__Print(val.front());

        // 打印行末分隔符后换行
        cout << __LINE_END << endl;

        // 出队列
        val.pop();
    }

    // 打印缩进与Queue容器类别的右定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__QueueTag, __CommonTag, T, N>::__Print(T val)
{
    // 打印缩进与Queue容器类别的左定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __QUEUE_BEGIN;

    // 不换行,依次打印Queue容器中的每个元素,以及元素之间的分隔符
    if (!val.empty())
    {
        cout << val.front();
        val.pop();

        while (!val.empty())
        {
            cout << __VALUE_SPLICE << val.front();
            val.pop();
        }
    }

    // 打印Queue容器类别的右定界符
    cout << __QUEUE_END;
}

怎么打印Tuple?虽然Tuple不能通过真正的for循环进行遍历,但我们可以使用编译期的“for循环”对Tuple进行“遍历”。请看以下示例:

// 以紧凑(单行)的打印格式打印Tuple
// 从“索引值”0,向“最大索引值”TopIdx进行“迭代”
template <typename T, int Idx, int TopIdx>
struct __PrintTupleOneLine
{

    static void __Print(const T &val);
};


// 当Idx == TopIdx时,“迭代”结束
template <typename T, int TopIdx>
struct __PrintTupleOneLine
{

    static void __Print(const T &val) {}
};


// 以宽松(多行)的打印格式打印Tuple
// 从“索引值”0,向“最大索引值”TopIdx进行“迭代”
template <typename T, int N, int Idx, int TopIdx>
struct __PrintTupleMultiLine
{

    static void __Print(const T &val);
};


// 当Idx == TopIdx时,“迭代”结束
template <typename T, int N, int TopIdx>
struct __PrintTupleMultiLine
{

    static void __Print(const T &val) {}
};


template <typename T, int Idx, int TopIdx>
void __PrintTupleOneLine::__Print(const T &val)
{
    // 打印“val[Idx]”
    cout << get(val);

    // 如果“val[Idx]”不是Tuple的最后一个元素,则打印元素之间的分隔符
    if (Idx + 1 < TopIdx)
    {
        cout << __VALUE_SPLICE;
    }

    // 继续打印“val[Idx + 1]”...
    __PrintTupleOneLine1, TopIdx>::__Print(val);
}


template <typename T, int N, int Idx, int TopIdx>
void __PrintTupleMultiLine::__Print(const T &val)
{
    // 缩进层数+1,使用“val[Idx]”的__PrintData版本打印“val[Idx]”
    __PrintData<

        typename __CategoryTraits<typename tuple_element::type>::__Category,

        typename __SubCategoryTraits<
            typename __CategoryTraits<typename tuple_element::type>::__Category,
            typename tuple_element::type
        >::__Category,

        typename tuple_element::type,

        N + 1

    >::__Print(get(val));

    // 打印行末分隔符后换行
    cout << __LINE_END << endl;

    // 继续打印“val[Idx + 1]”...
    __PrintTupleMultiLine1, TopIdx>::__Print(val);
}

此时,我们就可以提供用于打印Tuple的__PrintData的定义了:

// 打印__TupleTag类别 + 任意子类别组合的值,采用宽松的打印格式...
template <typename SubTag, typename T, int N>
void __PrintData<__TupleTag, SubTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Tuple容器类别的左定界符,然后换行
    cout << string(N * __INDENTATION_LEN, __SPACE) << __TUPLE_BEGIN << endl;

    // 调用多行版本的Tuple打印函数,打印val
    __PrintTupleMultiLine0, tuple_size::value>::__Print(val);

    // 打印Tuple容器类别的右定界符
    cout << __TUPLE_END;
}


// ...但是,如果子类别是__CommonTag,则采用紧凑的打印格式
template <typename T, int N>
void __PrintData<__TupleTag, __CommonTag, T, N>::__Print(const T &val)
{
    // 打印缩进与Tuple容器类别的左定界符
    cout << string(N * __INDENTATION_LEN, __SPACE) << __TUPLE_BEGIN;

    // 调用单行版本的Tuple打印函数,打印val
    __PrintTupleOneLine0, tuple_size::value>::__Print(val);

    // 打印Tuple容器类别的右定界符
    cout << __TUPLE_END;
}

至此,我们已经实现了print函数所需要的一切底层组件。现在我们需要做的,就是汇聚所有的这些底层组件,最终实现print函数。请看以下示例:

template <typename T>
void print(const T &val)
{
    __PrintData<

        // T的类别
        typename __CategoryTraits::__Category,

        // T的子类别
        typename __SubCategoryTraits<
            typename __CategoryTraits::__Category,
            T
        >::__Category,

        // val的类型
        T,

        // 缩进层数从0开始
        0

    >::__Print(val);

    cout << endl;
}

让我们立即来试试这个print函数的效果:

int main()
{
    // 普通值
    int sampleInt = 123;
    double *samplePtr = nullptr;
    string sampleStr = "abc";

    print(sampleInt);  // 123
    print(samplePtr);  // 0
    print(sampleStr);  // abc


    // 线性容器
    array<int, 3> sampleArray {123};
    vector<string> sampleVector {"abc""def""ghi"};
    list<dequestring>>> sampleComplexContainer {{{"abc", "def"}, {"ghi", "jkl"}}, {{"mno", "pqr"}, {"stu", "vwx"}}};

    print(sampleArray);   // [1, 2, 3]
    print(sampleVector);  // [abc, def, ghi]

    /*
    [
        [
            [abc, def],
            [ghi, jkl],
        ],
        [
            [mno, pqr],
            [stu, vwx],
        ],
    ]
    */

    print(sampleComplexContainer);


    // Map容器
    map<intstring> sampleMap {{1, "abc"}, {2, "def"}, {3, "ghi"}};
    multimap<intvector<string>> sampleComplexMap {{1, {"abc", "def"}}, {2, {"ghi""jkl"}}, {3, {"mno""pqu"}}};

    /*
    {
        (1: abc),
        (2: def),
        (3: ghi),
    }
    */

    print(sampleMap);

    /*
    {
        (
            1:
            [abc, def],
        ),
        (
            2:
            [ghi, jkl],
        ),
        (
            3:
            [mno, pqu],
        ),
    }
    */

    print(sampleComplexMap);


    // Set容器
    set<int> sampleSet {123};
    multiset<vector<bool>> sampleComplexSet {{true, false}, {false, true}, {true, false, false, true}};

    print(sampleSet);  // {1, 2, 3}

    /*
    {
        [0, 1],
        [1, 0],
        [1, 0, 0, 1],
    }
    */

    print(sampleComplexSet);


    // Pair
    pair<intstring> samplePair {1"abc"};
    pair<intvector<string>> sampleComplexPair {1, {"abc""def""ghi"}};

    print(samplePair);  // (1, abc)

    /*
    (
        1,
        [abc, def, ghi],
    )
    */

    print(sampleComplexPair);


    // Tuple容器
    tuple<intdoublecharstring> sampleTuple {12.'a'"abc"};
    tuple<intdoublecharstringvector<string>> sampleComplexTuple {12.'a'"abc", {"abc""def""ghi"}};

    print(sampleTuple);  // (1, 2, a, abc)

    /*
    (
        1,
        2,
        a,
        abc,
        [abc, def, ghi],
    )
    */

    print(sampleComplexTuple);


    // Stack容器
    stack<int> sampleStack;

    sampleStack.push(1);
    sampleStack.push(2);
    sampleStack.push(3);

    stack<vector<string>> sampleComplexStack;

    sampleComplexStack.push({"abc""def"});
    sampleComplexStack.push({"ghi""jkl"});
    sampleComplexStack.push({"mno""pqr"});

    /*
    栈底 --------> 栈顶
         [1, 2, 3]
    */

    print(sampleStack);

    /*
                     栈底
    [                 |
        [abc, def],   |
        [ghi, jkl],   |
        [mno, pqr],   |
    ]                 v
                     栈顶
    */

    print(sampleComplexStack);


    // Queue容器
    queue<int> sampleQueue;

    sampleQueue.push(1);
    sampleQueue.push(2);
    sampleQueue.push(3);

    priority_queue<vector<string>> sampleComplexPriorityQueue;

    sampleComplexPriorityQueue.push({"abc""def"});
    sampleComplexPriorityQueue.push({"ghi""jkl"});
    sampleComplexPriorityQueue.push({"mno""pqr"});

    /*
    队列首 <-------- 队列尾
           [1, 2, 3]
    */

    print(sampleQueue);

    /*
                     队列首
    [                  ^
        [mno, pqr],    |
        [ghi, jkl],    |
        [abc, def],    |
    ]                  |
                     队列尾
    */

    print(sampleComplexPriorityQueue);
}

至此,print函数的实现也就全部完成了。

5.3 本章后记

本章,我们首先通过一个简单的STL advance函数,讨论了编译期分派技术。这一函数的实现过程能够带给我们两点思考:

  1. 为什么我们能在用户无感知的情况下实现分派呢?不难发现:你,作为一个函数的实现者,有一样东西是用户所不具有的,那就是类型。当用户使用一个值来调用函数时,你不仅能得到这个值,还能“顺便”得到这个值的类型,而正是因为有了这多出来的类型,我们就能在类型上大做文章,实现出很多“神奇”的接口
  2. 事实上,并没有真正的“多功能函数”。但我们可以通过“添加中间层可解决一切问题”这一“经典定理”,使得单一的接口函数,根据传入的实参类型的不同,“多功能的”调用多个底层的实现函数,从而达到“多功能函数”的效果

紧接着,我们实现了一个代码更为复杂的print函数。观其输出结果,不禁让我们感慨:一个小小的T,在经过我们的“大做文章”之后,竟能够表现出如此丰富的多样性!这,就是编译期分派的强大威力所在!

6 “突破极限”的容器——Tuple

Tuple是一种非常特殊且高级的数据结构,其能够容纳和取用数量、类型都不定的一组值,你也可以将Tuple理解为某种“匿名结构体”。乍看之下,“数量、类型都不定”和模板中“什么都是已经确定的编译期常量”从语法上就是完全相悖的,和容器的“所有元素的类型必须相同”的原则也是完全相悖的,似乎,Tuple是一种“突破极限”的容器。可事实真的是如此吗?

6.1 可递归Pair

首先,请看下面这段“平淡无奇”的代码:

template <typename T1, typename T2>
struct __RecursionPair
{

public:

    // 数据成员
    T1 __first;
    T2 __second;


    // 构造函数
    __RecursionPair();
    __RecursionPair(const T1 &first, const T2 &second);
};


template <typename T1, typename T2>
__RecursionPair::__RecursionPair() = default;


template <typename T1, typename T2>
__RecursionPair::__RecursionPair(const T1 &first, const T2 &second):
    __first(first), __second(second) {}


// 针对只有一个值的Pair的特化
template <typename T1>
struct __RecursionPair
{

public:

    // 数据成员
    T1 __first;


    // 构造函数
    __RecursionPair();
    explicit __RecursionPair(const T1 &first);
};


template <typename T1>
__RecursionPairvoid>::__RecursionPair() = default;


template <typename T1>
__RecursionPairvoid>::__RecursionPair(const T1 &first): __first(first) {}


int main()
{
    __RecursionPair<intdouble> _(12.);
    __RecursionPair<intvoid> __(1);
}

“这不就是STL的Pair吗?”,你一定会有这样的疑问。没错,这确实就是STL的Pair,但请你继续看:

__RecursionPair<int, __RecursionPair<double, __RecursionPair<charstring>>> multiPair;  // 还有这种操作???

没错!就是有这样的操作。此时,也许你已经意识到了,只要“无限堆叠”这样的Pair,理论上就能实现一个任意数量+任意类型的容器了。我们称这样的Pair为“可递归Pair”。

故事结束了吗?不,这才只是个开始。我们可以立即发现,这种“无限堆叠”产生的“千层饼”,是一个非常反人类的东西,不仅没有“索引值”,甚至为了取得第10个值,竟然需要连着写9遍“.second”!这也太反人类了!请不要着急,接着往下看。

6.2 为可递归Pair的类型添加“索引值”

接下来,我们需要解决似乎很棘手的一个问题:如何根据“索引值”获取可递归Pair的某个位置的类型呢?要知道,可递归Pair里面可是根本没有“索引值”这一概念啊。

让我们先试着迈出第一步:获取__RecursionPair的T1。请看以下示例:

// 原型
// 通过typename __RecursionPairType::__ValueType获取“T[N]”的类型
template <int N, typename T>
struct __RecursionPairType;


// 获取__RecursionPair的第0个类型(相当于“T[0]”的类型)
template <typename T1>
struct __RecursionPairType<0, __RecursionPair>
{

    // __RecursionPair的第0个类型显然就是T1
    typedef T1 __ValueType;
};

似乎很顺利对不对?让我们继续:获取__RecursionPair的T1和T2。请看以下示例:

// 获取__RecursionPair的第0个类型(相当于“T[0]”的类型)
template <typename T1, typename T2>
struct __RecursionPairType<0, __RecursionPair>
{

    // __RecursionPair的第0个类型显然就是T1
    typedef T1 __ValueType;
};


// 获取__RecursionPair的第1个类型(相当于“T[1]”的类型)
template <typename T1, typename T2>
struct __RecursionPairType<1, __RecursionPair>
{

    // __RecursionPair的第1个类型显然就是T2
    typedef T2 __ValueType;
};

接下来,我们就要面对真正的难题了,它就是:

__RecursionPair>

仔细分析这一类型不难发现:T1和T2一定不会继续是一个__RecursionPair类型(因为我们人为地“默认”了可递归Pair只有second可以进行递归,实际上first也可以进行递归,但是这样的代码看上去比较“别扭”)。所以,我们立即可以给出以下实现:

// 获取__RecursionPair>的第0个类型(相当于“T[0]”的类型)
template <typename T1, typename T2, typename T3>
struct __RecursionPairType<0, __RecursionPair>>
{

    // 因为T1一定不会继续是一个__RecursionPair类型
    // 所以__RecursionPair>的第0个类型应该就是T1
    typedef T1 __ValueType;
};


// 获取__RecursionPair>的第1个类型(相当于“T[1]”的类型)
template <typename T1, typename T2, typename T3>
struct __RecursionPairType<1, __RecursionPair>>
{

    // 因为T2一定不会继续是一个__RecursionPair类型
    // 所以__RecursionPair>的第1个类型应该就是T2
    typedef T2 __ValueType;
};

那么,如果N大于1,要怎么办呢?此时,虽然我们自己已经无能为力(因为我们并没有能力“拆分”T3),但是我们可以“寄希望于”递归。请看以下示例:

// 获取__RecursionPair>的第N(N > 1)个类型(相当于“T[N]”的类型)
template <int N, typename T1, typename T2, typename T3>
struct __RecursionPairType>>
{

    // 如果N大于1,那么“T[N]”的类型应该是__RecursionPair的第N - 1个类型
    typedef typename __RecursionPairType1, __RecursionPair>::__ValueType __ValueType;
};

至此,我们就完整实现了根据“索引值”获取可递归Pair的某个位置的类型这一工具。让我们来看看效果:

int main()
{
    typedef __RecursionPair<int, __RecursionPair<double, __RecursionPair<charstring>>> Type;
    cout << typeid(__RecursionPairType<3, Type>::__ValueType).name();  // string
}

可以看出,输出结果完全符合我们的预期。

看到这里,也许你会觉得上述实现中的第4个和第5个特化(即__RecursionPairType<0, __RecursionPair>>和__RecursionPairType<1, __RecursionPair>>版本)似乎是多余的?你可以去掉这些特化,然后编译试试看。

6.3 为可递归Pair的值添加“索引值”

有了上文中__RecursionPairType的铺垫,根据“索引值”获取可递归Pair的某个位置的值这一功能似乎也可以“依葫芦画瓢”进行实现了。同样,让我们先迈出第一步:

// 原型
// 通过__RecursionPairValue::__Get(pairObj)获取“pairObj[N]”的值
template <int N, typename T>
struct __RecursionPairValue;


// 获取__RecursionPair的“pairObj[0]”的值
template <typename T1>
struct __RecursionPairValue<0, __RecursionPair>
{

    // __Get函数的参数类型显然就是__RecursionPair
    typedef __RecursionPairvoid> __PairType;


    // __Get函数的返回值类型显然就是T1
    typedef T1 __ValueType;


    // 实际的返回值显然就是pairObj.__first
    static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; }
    static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; }
};

让我们继续。接下来实现__RecursionPair的取值:

// 获取__RecursionPair的“pairObj[0]”的值
template <typename T1, typename T2>
struct __RecursionPairValue<0, __RecursionPair>
{

    // __Get函数的参数类型显然就是__RecursionPair
    typedef __RecursionPair __PairType;


    // __Get函数的返回值类型显然就是T1
    typedef T1 __ValueType;


    // 实际的返回值显然就是pairObj.__first
    static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; }
    static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; }
};


// 获取__RecursionPair的“pairObj[1]”的值
template <typename T1, typename T2>
struct __RecursionPairValue<1, __RecursionPair>
{

    // __Get函数的参数类型显然就是__RecursionPair
    typedef __RecursionPair __PairType;


    // __Get函数的返回值类型显然就是T2
    typedef T2 __ValueType;


    // 实际的返回值显然就是pairObj.__second
    static __ValueType &__Get(__PairType &pairObj) { return pairObj.__second; }
    static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__second; }
};

让我们继续。接下来实现__RecursionPair>的取值:

// 获取__RecursionPair>的“pairObj[0]”的值
template <typename T1, typename T2, typename T3>
struct __RecursionPairValue<0, __RecursionPair>>
{

    // __Get函数的参数类型显然就是__RecursionPair>
    typedef __RecursionPair> __PairType;


    // __Get函数的返回值类型显然就是T1
    typedef T1 __ValueType;


    // 实际的返回值显然就是pairObj.__first
    static __ValueType &__Get(__PairType &pairObj) { return pairObj.__first; }
    static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__first; }
};


// 获取__RecursionPair>的“pairObj[1]”的值
template <typename T1, typename T2, typename T3>
struct __RecursionPairValue<1, __RecursionPair>>
{

    // __Get函数的参数类型显然就是__RecursionPair>
    typedef __RecursionPair> __PairType;


    // __Get函数的返回值类型显然就是T2
    typedef T2 __ValueType;


    // 实际的返回值显然就是pairObj.__second.__first
    static __ValueType &__Get(__PairType &pairObj) { return pairObj.__second.__first; }
    static const __ValueType &__Get(const __PairType &pairObj) { return pairObj.__second.__first; }
};

那么,如果N大于1,要怎么办呢?我们需要解决两个问题:

  1. Get函数的返回值类型是什么?
  2. 怎么得到“pairObj[N]”的值?

第一个问题的解决方案不言而喻:我们已经实现了可以获取到可递归Pair的任意位置的类型的工具,这当然可以在这里为我们所用;对于第二个问题,我们同样可以基于递归,对pairObj.second进行“拆分”,直至N降至1为止。请看以下示例:

// 获取__RecursionPair>的“pairObj[N]”的值
template <int N, typename T1, typename T2, typename T3>
struct __RecursionPairValue>>
{

    // __Get函数的参数类型显然就是__RecursionPair>
    typedef __RecursionPair> __PairType;


    // __Get函数的返回值类型需要依赖我们前面已经实现的__RecursionPairType获取
    typedef typename __RecursionPairType::__ValueType __ValueType;


    // 我们并没有能力“拆分”pairObj.__second.__second,但是我们可以“寄希望于”递归
    static __ValueType &__Get(__PairType &pairObj)
    {
        return __RecursionPairValue1, __RecursionPair>::__Get(pairObj.__second);
    }


    // 同上
    static const __ValueType &__Get(const __PairType &pairObj)
    {
        return __RecursionPairValue1, __RecursionPair>::__Get(pairObj.__second);
    }
};

至此,我们就完整实现了根据“索引值”获取可递归Pair的某个位置的值这一工具。让我们来看看效果:

int main()
{
    __RecursionPair<int, __RecursionPair<double, __RecursionPair<charstring>>> testPair;
    __RecursionPairValue<3decltype(testPair)>::__Get(testPair) = "abc";
    cout << __RecursionPairValue<3decltype(testPair)>::__Get(testPair);  // abc
}

同样,如果你觉得上述实现中的第4个和第5个特化(即__RecursionPairValue<0, __RecursionPair>>和__RecursionPairValue<1, __RecursionPair>>版本)是多余的,你可以去掉这些特化,然后编译试试看。

6.4 将“千层饼”擀成“单层饼”

本节将会是整个Tuple的实现中最为精彩的部分!

我们虽然已经实现了针对可递归Pair的取类型和取值工具,但我们还是没有实现出一个“扁平的”真正的Tuple(没错,终于又看到Tuple这个词了)。接下来,我们就开始着手考虑,如何将可递归Pair这张“千层饼”擀平,变成一张“单层饼”Tuple。

如何实现“擀平”这一操作呢?稍加思考不难发现,可递归Pair和Tuple之间似乎存在着这样的一一对应关系:

  • 含有一个元素的Tuple,就是一个__RecursionPair
  • 含有两个元素的Tuple,就是一个__RecursionPair
  • 含有三个元素的Tuple,就是一个__RecursionPair>
  • 含有四个元素的Tuple,就是一个__RecursionPair>>
  • ...

如何描述这种“是一个”的语义?哦!是继承!

请看以下示例:

// 原型
// 通过Tuple构造一个Tuple
template <typename... Types>
struct Tuple;


// 含有一个元素的Tuple,就是一个__RecursionPair
template <typename T1>
struct Tuple: __RecursionPairvoid>
{
    // 我是一个怎样的__RecursionPair?当然是继承的那个!
    typedef __RecursionPairvoid> __PairType;


    // 构造函数(待实现)
    Tuple();
    Tuple(const T1 &first);
};


// 含有两个元素的Tuple,就是一个__RecursionPair
template <typename T1, typename T2>
struct Tuple: __RecursionPair
{
    // 我是一个怎样的__RecursionPair?当然也是继承的那个!
    typedef __RecursionPair __PairType;


    // 构造函数(待实现)
    Tuple();
    Tuple(const T1 &first, const T2 &second);
};


// 默认构造函数
template <typename T1>
Tuple::Tuple() = default;


// 只需要调用Tuple的“可递归Pair形态”(即父类)的构造函数即可
template <typename T1>
Tuple::Tuple(const T1 &first): __PairType(first) {}


// 默认构造函数
template <typename T1, typename T2>
Tuple::Tuple() = default;


// 同样,只需要调用Tuple的“可递归Pair形态”(即父类)的构造函数即可
template <typename T1, typename T2>
Tuple::Tuple(const T1 &first, const T2 &second): __PairType(first, second) {}

那么,含有不止两个元素的Tuple,是哪个可递归Pair呢?如果你已经注意到了上面的两个Tuple实现中的“看似无用”的typedef,那么问题就能迎刃而解。这些typedef,保存了当前Tuple所对应的“可递归Pair形态”。从可递归Pair的角度去思考,不难找到以下规律:

  • Tuple的“可递归Pair形态”是__RecursionPair::__PairType>,即:将T1,与Tuple的“可递归Pair形态”放入一个__RecursionPair中,最终得到的结果是__RecursionPair>
  • Tuple的“可递归Pair形态”是__RecursionPair::__PairType>,即:将T1,与Tuple的“可递归Pair形态”放入一个__RecursionPair中(Tuple的“可递归Pair形态”就是上面已经得到的Tuple的“可递归Pair形态”),最终得到的结果是__RecursionPair>>
  • ...
  • Tuple的“可递归Pair形态”是__RecursionPair::__PairType>,即:将T1,与Tuple的“可递归Pair形态”放入一个__RecursionPair中,最终得到的结果是__RecursionPair>>...>

找到了这一规律,代码实现也就轻而易举了。请看以下示例:

// Tuple的“可递归Pair形态”是:将T1,与typename Tuple::__PairType
//(即Tuple的“可递归Pair形态”)放入一个__RecursionPair中
template <typename T1, typename... Types>
struct Tuple: __RecursionPairtypename Tuple::__PairType>
{
    // 我是一个怎样的__RecursionPair?同样也是继承的那个!
    typedef __RecursionPairtypename Tuple::__PairType> __PairType;


    // 构造函数(待实现)
    Tuple();
    Tuple(const T1 &first, const Types &... Args);
};

那么,这样的一个含有多个元素的Tuple,其构造函数又该如何实现呢?通过上文的讨论,我们不难发现:不管是什么样的Tuple(从只含有一个元素的Tuple到含有很多个元素的Tuple),其父类都是一个可递归Pair,而可递归Pair也是Pair,其构造函数永远只需要两个值(不管是多么复杂的可递归Pair)。所以,我们仍然可以通过直接调用父类的构造函数来对任意的Tuple进行构造。我们需要哪两个值来调用复杂的可递归Pair的构造函数呢?让我们继续进行“找规律”:

  • 为了构造一个Tuple(Arg1, Arg2, Arg3),我们需要构造一个typename Tuple::__PairType,即一个__RecursionPair>,其中,T1来自于Arg1,而__RecursionPair可以通过构造一个Tuple(Arg2, Arg3)得到(因为一个Tuple就是一个__RecursionPair
  • 为了构造一个Tuple(Arg1, Arg2, Arg3, Arg4),我们需要构造一个typename Tuple::__PairType,即一个__RecursionPair>>,其中,T1同样来自于Arg1,而__RecursionPair>可以通过构造一个Tuple(Arg2, Arg3, Arg4)得到(因为一个Tuple就是一个__RecursionPair>)
  • ...
  • 为了构造一个Tuple(first, Args...),我们需要构造一个typename Tuple::__PairType,即一个__RecursionPair::__PairType>,其中,T1来自于first,而typename Tuple::__PairType可以通过构造一个Tuple(Args...)得到(因为一个Tuple就是一个typename Tuple::__PairType)

我们再一次通过找规律的方法得到了结论!接下来就可以进行代码实现了。请看以下示例:

// 默认构造函数
template <typename T1, typename... Types>
Tuple::Tuple() = default;


// 只需要调用Tuple的“可递归Pair形态”(即父类)的构造函数即可
// 构造__PairType的两个参数分别来自于first与构造Tuple(Args...)所得到的一个__RecursionPair
template <typename T1, typename... Types>
Tuple::Tuple(const T1 &first, const Types &... Args):
    __PairType(first, Tuple(Args...)) {}

至此,Tuple的实现中最重要的部分:Tuple的构造函数,也就全部实现完毕了。让我们立即来试用一下。请看以下示例:

int main()
{
    Tuple<intdoublecharstring> sampleTuple(12.'3'"4");

    cout << __RecursionPairValue<0decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl;  // 1
    cout << __RecursionPairValue<1decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl;  // 2
    cout << __RecursionPairValue<2decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl;  // 3
    cout << __RecursionPairValue<3decltype(sampleTuple)::__PairType>::__Get(sampleTuple) << endl;  // 4
}

由此可见,Tuple的构造函数工作正常(虽然我们暂时还只能通过“可递归Pair时代”的工具获取到其内部的值)。

6.5 Tuple的其他功能的实现

最后,就是一些简单的周边功能的实现了。

首先是MakeTuple快捷函数,此函数只需要使用一个可变参数模板封装Tuple的构造函数即可。请看以下示例:

template <typename... Types>
inline Tuple MakeTuple(const Types &... Args)
{
    return Tuple(Args...);
}


int main()
{
    auto sampleTuple = MakeTuple(12.'3');
}

然后是根据“索引值”获取Tuple的某个位置的类型的类,实现时只需要将全部操作直接委托给我们已经实现的__RecursionPairType即可。请看以下示例:

// 原型
// 通过typename TupleType::Type获取“T[N]”的类型
template <int N, typename T>
struct TupleType;


// 仅当T是一个Tuple时,此类才有意义
template <int N, typename... Types>
struct TupleType>
{

    // 使用__RecursionPairType作用于Tuple的“可递归Pair形态”上,就能获取“Tuple[N]”的类型
    typedef typename __RecursionPairTypetypename Tuple::__PairType>::__ValueType Type;
};


int main()
{
    Tuple<intdoublecharstring> sampleTuple(12.'3'"4");

    cout << typeid(TupleType<0decltype(sampleTuple)>::Type).name() << endl;  // int
    cout << typeid(TupleType<1decltype(sampleTuple)>::Type).name() << endl;  // double
    cout << typeid(TupleType<2decltype(sampleTuple)>::Type).name() << endl;  // char
    cout << typeid(TupleType<3decltype(sampleTuple)>::Type).name() << endl;  // string
}

然后是根据“索引值”获取Tuple的某个位置的值的函数,实现时只需要将全部操作直接委托给我们已经实现的TupleType以及__RecursionPairValue即可。请看以下示例:

// 函数的返回值就是typename TupleType>::Type
template <int N, typename... Types>
inline typename TupleType>::Type &Get(Tuple &tupleObj)
{
    // 使用__RecursionPairValue作用于Tuple的“可递归Pair形态”上,就能获取“tupleObj[N]”的值
    return __RecursionPairValuetypename Tuple::__PairType>::__Get(tupleObj);
}


// 同上
template <int N, typename... Types>
inline const typename TupleType>::Type &Get(const Tuple &tupleObj)
{
    return __RecursionPairValuetypename Tuple::__PairType>::__Get(tupleObj);
}


int main()
{
    Tuple<intdoublecharstring> sampleTuple(12.'3'"4");

    cout << Get<0>(sampleTuple) << endl;  // 1
    cout << Get<1>(sampleTuple) << endl;  // 2
    cout << Get<2>(sampleTuple) << endl;  // 3
    cout << Get<3>(sampleTuple) << endl;  // 4
}

最后是获取Tuple的长度的类,直接使用sizeof...(Types)即可。请看以下示例:

// 原型
template <typename T>
struct TupleSize;


// 仅当T是一个Tuple时,此类才有意义
template <typename... Types>
struct TupleSize>
{

    // Tuple的长度显然就是Tuple的可变模板参数的数量
    static constexpr int Size = sizeof...(Types);
};


int main()
{
    cout << TupleSizeint, doublecharstring>>::Size << endl;  // 4
}

至此,Tuple的实现也就全部完成了。

6.6 本章后记

Tuple,作为一个看起来已然“突破极限”的高级容器,其背后的核心竟然只是一个“平淡无奇”的Pair,这不得不令人惊讶于基于模板的高阶抽象的威力。在Tuple的实现过程中,我们充分利用了模板偏特化,用以描绘出各种不同“形态”的可递归Pair;我们也使用了继承,用以描绘出Tuple与可递归Pair的一一对应关系。在这里,模板与继承,这两个“不同世界的产物”,被巧妙的结合在了一起,最终为我们带来了一场十分精彩的二重奏!

7 模板与高性能计算的极佳配合——表达式模板

表达式模板?什么?你没听说过?那就对了!通过本章的讨论,你就会了解到:模板是如何在用户无感知的前提下,将高性能计算引入我们的程序中的。

7.1 表达式的困境

让我们从一个看似很简单的问题开始:

如何实现向量的加法运算?

如果使用STL的array表示向量,不难做出以下实现:

template <typename T, size_t N>
array operator+(const array &lhs, const array &rhs)
{
    array resArray;

    for (int idx = 0; idx < N; idx++) resArray[idx] = lhs[idx] + rhs[idx];

    return resArray;
}


int main()
{
    array<int, 3> lhs {123}, rhs {456}, res;

    res = lhs + rhs;

    for (auto val: res) cout << val << endl;  // 5 7 9
}

这个实现有什么问题呢?请看以下示例:

lhs + rhs + lhs + rhs + lhs + rhs + lhs + rhs + lhs + rhs;  // 哦!大量的冗余计算!

在上面这个“10连加”表达式中,operator+函数一共被调用了9次,这也就意味着:函数体内的resArray临时变量被创建、return了9次(假设没有NRV优化),for循环也被执行了9次,而这还只是一次“10连加”所造成的结果。可想而知,在计算量变得越来越大时,这是多么大的时间耗费!此时,我们不难想到,上述的“10连加”表达式实际上能够被优化为如下实现:

// 实际上只需要一次函数调用
template <typename T, size_t N>
array operator+(const array &lhs, const array &rhs)
{
    array resArray;

    // 实际上也只需要一次循环
    for (int idx = 0; idx < N; idx++)
    {
        // 只需要在循环体内执行“10连加”即可
        resArray[idx] = lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx] + lhs[idx] +
            rhs[idx] + lhs[idx] + rhs[idx] + lhs[idx] + rhs[idx];
    }

    return resArray;
}

可问题是,编译器就算有能力优化成这样的实现,其也不能优化成这样。这是由于C++的表达式语义本就是“积极主动的”,当编译器看到lhs + rhs...时,其就必须遵守C++的语义规定,立即计算此加法,而“暂且不顾”后续表达式。

看来,编译器优化是彻底不可能帮得上忙了。这让我们陷入了困境之中。

7.2 一个天马行空的想法

既然编译器帮不上忙,那我们是否能通过某种技术,“绕过”编译器的这种主动计算呢?如果你的想象力足够丰富,也许你会有这样的想法:

能否将表达式看作某种“字符串”,这样,加法就相当于“字符串的拼接”呢?而当我们真的需要表达式的结果时,我们可以实现一个对“表达式字符串”进行求值的函数来进行求值。

这是一个天马行空的想法,但基于模板,这个想法是真的可以实现的!这就是本章将要讨论的表达式模板技术。

7.3 向量类的实现

首先,让我们实现一个Array类,用于存放一个向量。请看以下示例:

template <typename T, int N>
class __Array
{

public:

    // 构造函数
    __Array();
    explicit __Array(const T &val);
    __Array(initializer_list initializerList);


    // operator[]
    T &operator[](int idx) { return __data[idx]; }
    const T &operator[](int idx) const { return __data[idx]; }


private:

    // 一个C语言数组,用于存放向量
    T __data[N];
};


template <typename T, int N>
__Array::__Array() = default;


template <typename T, int N>
__Array::__Array(const T &val)
{
    for (int idx = 0; idx < N; idx++)
    {
        __data[idx] = val;
    }
}


template <typename T, int N>
__Array::__Array(initializer_list initializerList)
{
    int idx = 0;

    for (auto &val: initializerList)
    {
        __data[idx++] = val;
    }
}


int main()
{
    __Array<int3> lhs {123};

    for (int idx = 0; idx < 3; idx++) cout << lhs[idx] << endl;  // 1 2 3
}

我们为这个Array实现了默认构造函数,Fill构造函数,initializer_list构造函数,以及operator[]重载。看上去平淡无奇,不是吗?

7.4 “表达式字符串”的实现

接下来,我们就来实现上文中的“表达式字符串”(当然,我们不是真的去实现一个特殊的字符串)。一个“表达式字符串”,如“lhs + rhs”,是由哪几部分组成的呢?显然,其是由lhs、“+”以及rhs组成,其中,lhs与rhs代表的是某个值,而“+”代表的是一个动作。如果我们使用两个变量分别存放lhs与rhs,并使用一个函数表达“+”这一动作,我们就能够实现出一个“表达式字符串”了。而将这些内容封装进一个模板中,我们也就得到了一个“表达式模板”。请看以下示例:

// 加法表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Plus
{

public:

    // 构造函数
    __Plus(const LExpr &lhs, const RExpr &rhs);


    // 当对这个表达式模板进行[...]运算的时候,就能得到这个表达式模板在某个“索引值”位置上的加法计算的结果
    // 也就是说,表达式模板也是某种从外观上看和向量别无二致的东西
    T operator[](int idx) const { return __lhs[idx] + __rhs[idx]; }


private:

    // 用于保存LExpr与RExpr的引用的数据成员
    const LExpr &__lhs;
    const RExpr &__rhs;
};


template <typename T, typename LExpr, typename RExpr>
__Plus::__Plus(const LExpr &lhs, const RExpr &rhs):
    __lhs(lhs), __rhs(rhs) {}

__Plus的模板参数包含加法的返回值类型T,以及左右值类型LExpr和RExpr;在__Plus中,我们声明了两个分别指向LExpr和RExpr的引用;而在构造函数中,lhs、rhs被分别绑定至类中的两个引用上,此时,我们并没有执行任何加法运算。那么,什么时候才执行加法运算呢?从代码中不难看出,直到operator[]时,才会真正计算加法。

这是一种利用模板实现的“惰性计算”技术,当加法语义出现时,我们并没有真的执行加法,而只是执行了一次成本很低的“记录”操作,我们记录了执行一次加法所需要的全部信息:左值、加这个动作、以及右值。仅当真正需要加法的结果时,__Plus才会“在我们强硬的驱使下”计算加法。并且,就算是在这种“强硬的驱使下”,__Plus每次也只会计算一个位置的加法。这就使得__Plus能够最大程度的规避无意义的加法计算(设想我们进行了一次十万维向量的加法,但我们只需要知道第五万维这一个位置的加法结果)。

此外,__Plus在设计上刻意的模仿了__Array的操作,这就使得__Plus也能够像一个__Array那样具有“索引值”。这样做的意义是什么呢?仅仅是为了方便、美观吗?我们将在下一节中揭晓答案。

接下来,让我们试着使用一下__Plus类,体验一下这种“惰性计算”技术。请看以下示例:

int main()
{
    __Array<int3> lhs {123}, rhs {456};

    // 保存了lhs + rhs这个表达式,但不对其进行计算
    __Plus<int, __Array<int3>, __Array<int3>> res(lhs, rhs);

    for (int idx = 0; idx < 3; idx++)
    {
        // 这里才计算加法
        cout << res[idx] << endl;
    }
}

看到这里,也许你会恍然大悟:“哦!这个__Plus和上一章的可递归Pair一样,也是可以递归的!”请看以下示例:

int main()
{
    __Array<int3> lhs {123}, rhs {456};

    // 保存了lhs + rhs + lhs这个表达式,但不对其进行计算
    // 可是这也太反人类了吧!
    __Plus<int, __Array<int3>, __Array<int3>> tmp(lhs, rhs);
    __Plus<int, __Plus<int, __Array<int3>, __Array<int3>>, __Array<int3>> res(tmp, lhs);

    for (int idx = 0; idx < 3; idx++)
    {
        // 这里才计算加法
        cout << res[idx] << endl;
    }
}

我们通过整整两行的“超长”代码,“终于”实现了lhs + rhs + lhs的惰性加法。显然,这样的实现是非常“反人类”的,有什么办法能对其进行简化,甚至让用户无感知呢?稍加思索就能够发现,只要使用运算符重载,我们就能把所有这些都隐藏于幕后,只留下lhs + rhs + lhs本身。请看以下示例:

template 
/* 这里写什么?*/ operator+(const /* 这里写什么?*/ &lhs, const /* 这里写什么?*/ &rhs)
{
    return /* 这里写什么?*/;
}


int main()
{
    /* 这里写什么?*/ lhs {123}, rhs {456}, res;

    // 最终的效果,太棒了!
    res = lhs + rhs + lhs;

    for (int idx = 0; idx < 3; idx++)
    {
        cout << res[idx] << endl;
    }
}

对于operator+,其需要同时满足“__Array + __Array”、“__Array + __Plus”、“__Plus + __Array”、“__Plus + __Plus”等等的“排列组合”(并且,请不要忘了:除了“加”,还有“减乘除”呢!)。这就使得我们难以确定lhs与rhs的类型。难道真的要为每种情况都写一个operator+重载吗?请接着往下看。

7.5 再加一层抽象

如何规避这种“排列组合”呢?让我们开拓一下思维,不难发现:单独的一个__Array是一个表达式,而__Plus(任意两个表达式相加的结果)也是一个表达式,并且他们的共性即在于,都是可以基于operator[]进行表达式求值的。至此,解决方案水落石出:我们可以在__Array和__Plus之上再增加一个抽象层,表达“表达式”语义,而__Array和__Plus在此抽象层中并无区别,都是一个可以进行operator[]运算的“表达式”。此时你应该能够明白:为什么__Plus要“刻意”模仿__Array的operator[]了。请看以下示例:

template <typename T, int N, typename Expr>
class __Expression
{

public:

    // 适用于__Array的构造函数
    __Expression();
    explicit __Expression(const T &val);
    __Expression(initializer_list initializerList);


    // 适用于__Plus的构造函数
    __Expression(const Expr &expr);


    // operator[]直接委托给__expr执行
    T &operator[](int idx) { return __expr[idx]; }
    T operator[](int idx) const { return __expr[idx]; }


    // operator=
    template <typename RExpr>
    __Expression &operator=(const __Expression &rhs);


private:

    // __expr可能是一个__Array,也可能是一个__Plus
    Expr __expr;
};


template <typename T, int N, typename Expr>
__Expression::__Expression() = default;


template <typename T, int N, typename Expr>
__Expression::__Expression(const T &val): __expr(val) {}


template <typename T, int N, typename Expr>
__Expression::__Expression(initializer_list initializerList): __expr(initializerList) {}


template <typename T, int N, typename Expr>
__Expression::__Expression(const Expr &expr): __expr(expr) {}


// operator=直接委托给__expr执行
// 直到operator=发生时,rhs才会真正被计算
template <typename T, int N, typename Expr>
template <typename RhsExpr>
__Expression &__Expression::operator=(
    const __Expression &rhs)
{
    for (int idx = 0; idx < N; idx++)
    {
        // 计算rhs[idx]的值,并赋值给左值
        __expr[idx] = rhs[idx];
    }

    return *this;
}

让我们来分析这一实现:既然我们需要将__Array和__Plus都抽象为一个表达式,那么我们就可以增加一个模板参数Expr,用以标明这个__Expression到底是什么(是__Array还是__Plus)。由于Expr既可以是__Array又可以是__Plus,我们就需要实现多个构造函数,使得这两种类型的值都可以在__Expression中构造。所以,我们实现了三个和__Array的三个构造函数功能一致的构造函数,以及可以使用一个Expr作为参数的构造函数。然后,我们将operator[]和operator=都直接委托给__expr执行。

显然,当用户在使用的时候,__Expression的Expr模板参数必须是__Array,所以我们可以声明一个固定了Expr模板参数的模板,作为面向用户的Array接口。请看以下示例:

// 最终面向用户的Array接口
template <typename T, int N>
using Array = __Expression>;

同时,作为实现者,我们也可以创造一些更复杂的Expr模板参数。所以,就让我们来实现上一节中未能实现的operator+吧。请看以下示例:

// __Expression + __Expression的结果是一个新的__Expression
// 其第一、二模板参数不变,第三模板参数是LExpr + RExpr的结果,即__Plus
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator+(
    const __Expression &lhs, const __Expression &rhs)
{
    // 用lhs与rhs的__expr,构造出一个__Plus
    // 再用这个__Plus,构造出一个新的__Expression(使用的是__Expression的第四构造函数)
    return __Expression>(
        __Plus(lhs.__expr, rhs.__expr));
}

看上去很复杂的样子?让我们来分析一下。首先,由于我们并不知道lhs和rhs的Expr分别是什么(二者都可能是__Array,如果这是一个“新的”Array;或__Plus,如果这已经是一个表达式),所以我们需要两个模板参数LExpr与RExpr,以分别代表lhs和rhs的Expr类型;但同时我们知道,只有相同类型的Array之间可以进行运算,所以我们只需要一套T与N即可。所以,两个形参分别是const Array &lhs与const Array &rhs。

返回值是什么呢?不难发现,当__Expression与__Expression相加后,结果仍然是一个新的__Expression,而真正需要相加的其实是LExpr与RExpr,且相加的结果是__Plus(事实上,相加的结果也可以就是__Plus, __Expression>,你一定不难想出其中原因。但很明显,这样的代码也实在是太长,太反人类了),故返回值的类型就是一个Expr模板参数为__Plus的__Expression,即__Expression>,而实际的返回值需要先使用lhs与rhs的__expr,构造出一个__Plus,再用这个__Plus,构造出一个新的__Expression。这里使用的是__Expression的第四构造函数。

现在,让我们试着使用一下我们刚刚实现的Array(请注意:如果你现在就实际编译以下这段代码,请去除__Expression的__expr的private限定,或为operator+函数进行友元授权)。请看以下示例:

int main()
{
    // lhs,rhs,res的实际类型都是__Expression>
    Array<int3> lhs {123}, rhs {456}, res;

    // “什么都不做”(不会进行实际的运算)
    lhs + rhs + lhs;

    // lhs + rhs...的类型是__Expression, __Array>>
    // ... + lhs的类型是__Expression, __Array>, __Array>>
    // 直到operator=发生时,才会进行实际的运算
    res = lhs + rhs + lhs;

    for (int idx = 0; idx < 3; idx++)
    {
        // 此时,res[idx]就是一次指针运算
        cout << res[idx] << endl;
    }
}

观察上述代码不难发现,我们仅仅才做了两次加法,__Expression的类型就已经“长的没法看”了。但是事实上,我们根本就不用关心__Expression的类型究竟是什么,而只需要牢记以下两点即可:

  1. 不管进行多少次计算,或一次计算都还没有进行(即一个Array),我们所操作的都是一个(可能具有很复杂类型的)__Expression
  2. 对__Expression进行operator[],就相当于对__Expression中的__expr进行operator[];而__expr无非只有两种情况:其要么是一个__Array,此时,operator[]就相当于一次数组取值;要么是一个__Plus,此时,operator[]就相当于递归地调用lhs[idx] + rhs[idx],直到lhs与rhs都不再是一个__Plus为止

由此可见,__Expression一方面为用户提供了对于表达式模板的存在无感知的Array类,另一方面又丝毫不丢失其作为表达式模板的功能,实在是一个优秀的“左右开弓”类;另一方面,由于我们的__Array与__Plus均实现了统一的operator[]接口,这就使得__Expression能够“自适应地”最终实现对其自身的求值。以上种种,都能够为我们展现出“抽象”这一思想的精彩之处。

7.6 让标量也加入进来

在数学中,一个向量不仅可以和另一个向量相加,还可以和一个标量(即一个T类型的值)相加。本节我们就来实现这一功能。

如何让标量也加入我们的“__Expression大家族”中呢?没错,关键就在于我们在上几节已经“老生常谈”的operator[]。虽然标量根本就没有operator[]这一概念,我们也可以“强行的”为其添加这一概念,以使其适配__Expression的需要。请看以下示例:

// 为标量提供的封装类,从而使得标量也能够适配__Expression
template <typename T>
class __Scalar
{

public:

    // 构造函数
    __Scalar(T val);


    // 强行为标量提供一个“莫名其妙的”operator[]
    // 不管索引值是多少(事实上我们根本就无视了这个索引值),都返回val
    T operator[](intconst { return __val; }


private:

    // 使用一个T类型的数据成员存放构造函数中的val
    T __val;
};


template <typename T>
__Scalar::__Scalar(T val): __val(val) {}

此时,__Expression的Expr模板参数就不仅可以是__Array或__Plus,还可以是__Scalar了。

让我们继续,实现适用于__Expression与__Scalar之间的加法的运算符重载。请看以下示例:

// __Expression + T
// 其结果为__Plus>
template <typename T, int N, typename LExpr>
inline __Expression>> operator+(
    const __Expression &lhs, const T &rhs)
{
    // 先使用rhs构造出一个__Scalar
    // 再使用lhs的__expr和__Scalar构造出一个__Plus
    // 最后使用一个__Plus构造出一个新的__Expression
    return __Expression>>(
        __Plus>(lhs.__expr, __Scalar(rhs)));
}


// T + __Expression
// 其结果为:__Plus, RExpr>
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator+(
    const T &lhs, const __Expression &rhs)
{
    // 先使用lhs构造出一个__Scalar
    // 再使用__Scalar和rhs的__expr构造出一个__Plus
    // 最后使用一个__Plus构造出一个新的__Expression
    return __Expression, RExpr>>(
        __Plus, RExpr>(__Scalar(lhs), rhs.__expr));
}

在我们试用这一功能前,其实还有一件事是没有完成的。请设想:当我们写下“lhs + 1”这一表达式时,这里的“1”显然是一个临时量,而如果使用我们现在所实现的__Plus,那么这个临时量“1”将被存入一个引用中。这将立即导致“悬挂引用”的发生!所以,我们需要实现一个简单的Traits类,在面对一个__Scalar时,将__Plus中的数据成员类型,从引用类型自动切换至值类型。这一Traits的实现非常简单,请看以下示例:

// 不管T是是什么,都萃取出一个const T &类型...
template <typename T>
struct __ScalarTypeTraits
{

    typedef const T &__Type;
};


// ...但是,如果T是一个__Scalar类型,则萃取出一个__Scalar类型
template <typename T>
struct __ScalarTypeTraits<__Scalar>
{

    typedef __Scalar __Type;
};

有了这个Traits,我们就可以使用这个Traits改进我们的__Plus类了。请看以下示例:

template <typename T, typename LExpr, typename RExpr>
class __Plus
{


// ...


private:

    // 原实现:
    // const LExpr &__lhs;
    // const RExpr &__rhs;

    // 改进后的实现:
    // 在LExpr(或RExpr)为__Scalar(或__Scalar)时
    // __Type将从引用类型自动切换至值类型
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};

至此,我们就可以将标量也加入到表达式模板中了(请注意:如果你现在就实际编译以下这段代码,请去除__Expression的__expr的private限定,或为operator+函数进行友元授权)。请看以下示例:

int main()
{
    Array<int3> lhs {123}, rhs {456}, res;

    // 加入标量
    res = lhs + rhs + lhs + 1;

    // 7 10 13
    for (int idx = 0; idx < 3; idx++)
    {
        cout << res[idx] << endl;
    }
}

本节中,我们通过一个对标量的简单的封装类,使得标量也能够加入到表达式模板中;同时,为了避免标量临时量所引发的“悬挂引用”问题,我们又实现了一个简单的Traits类,用于在面对标量时自动将表达式模板中的引用类型切换为值类型。

至此,表达式模板的全部技术就都讨论完毕了。下一节,我们将最终给出表达式模板的完整实现。

7.7 完整的实现

由于本章的代码较为分散,且我们仍有很多重复性的代码没有于上文中给出。故本节中,我们将给出表达式模板的完整实现。主要包含以下几点新增内容:

  1. 我们不仅需要实现operator+,还要实现operator-、operator*、operator/和operator%。这些实现都是operator+的简单重复
  2. 我们需要为所有的operator函数添加友元授权
  3. 我们需要为__Expression实现operator<<重载(实现方案与operator=一致)

请看以下示例:

// “梦开始的地方”:__Array类
template <typename T, int N>
class __Array
{

public:

    // 构造函数
    __Array();
    explicit __Array(const T &val);
    __Array(initializer_list initializerList);


    // operator[]
    T &operator[](int idx) { return __data[idx]; }
    const T &operator[](int idx) const { return __data[idx]; }


private:

    // 数据成员
    T __data[N];
};


template <typename T, int N>
__Array::__Array() = default;


template <typename T, int N>
__Array::__Array(const T &val)
{
    for (int idx = 0; idx < N; idx++)
    {
        __data[idx] = val;
    }
}


template <typename T, int N>
__Array::__Array(initializer_list initializerList)
{
    int idx = 0;

    for (auto &val: initializerList)
    {
        __data[idx++] = val;
    }
}


// 标量适配器
template <typename T>
class __Scalar
{

public:

    // 构造函数
    __Scalar(T val);


    // operator[]
    T operator[](intconst { return __val; }


private:

    // 数据成员
    T __val;
};


template <typename T>
__Scalar::__Scalar(T val): __val(val) {}


// 标量值类型萃取器
template <typename T>
struct __ScalarTypeTraits
{

    typedef const T &__Type;
};


template <typename T>
struct __ScalarTypeTraits<__Scalar>
{

    typedef __Scalar __Type;
};


// 加法表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Plus
{

public:

    // 构造函数
    __Plus(const LExpr &lhs, const RExpr &rhs);


    // operator[]
    T operator[](int idx) const { return __lhs[idx] + __rhs[idx]; }


private:

    // 数据成员
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};


// 减法表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Minus
{

public:

    // 构造函数
    __Minus(const LExpr &lhs, const RExpr &rhs);


    // operator[]
    T operator[](int idx) const { return __lhs[idx] - __rhs[idx]; }


private:

    // 数据成员
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};


// 乘法表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Multiplies
{

public:

    // 构造函数
    __Multiplies(const LExpr &lhs, const RExpr &rhs);


    // operator[]
    T operator[](int idx) const { return __lhs[idx] * __rhs[idx]; }


private:

    // 数据成员
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};


// 除法表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Divides
{

public:

    // 构造函数
    __Divides(const LExpr &lhs, const RExpr &rhs);


    // operator[]
    T operator[](int idx) const { return __lhs[idx] / __rhs[idx]; }


private:

    // 数据成员
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};


// 取模表达式模板
template <typename T, typename LExpr, typename RExpr>
class __Modulus
{

public:

    // 构造函数
    __Modulus(const LExpr &lhs, const RExpr &rhs);


    // operator[]
    T operator[](int idx) const { return __lhs[idx] % __rhs[idx]; }


private:

    // 数据成员
    typename __ScalarTypeTraits::__Type __lhs;
    typename __ScalarTypeTraits::__Type __rhs;
};


template <typename T, typename LExpr, typename RExpr>
__Plus::__Plus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}


template <typename T, typename LExpr, typename RExpr>
__Minus::__Minus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}


template <typename T, typename LExpr, typename RExpr>
__Multiplies::__Multiplies(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}


template <typename T, typename LExpr, typename RExpr>
__Divides::__Divides(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}


template <typename T, typename LExpr, typename RExpr>
__Modulus::__Modulus(const LExpr &lhs, const RExpr &rhs): __lhs(lhs), __rhs(rhs) {}


// __Expression表达式类
template <typename T, int N, typename Expr>
class __Expression
{

public:

    // 构造函数
    __Expression();
    explicit __Expression(const T &val);
    __Expression(initializer_list initializerList);
    __Expression(const Expr &expr);


    // operator[]
    T &operator[](int idx) { return __expr[idx]; }
    T operator[](int idx) const { return __expr[idx]; }


    // operator=
    template <typename RExpr>
    __Expression &operator=(const __Expression &rhs);


private:

    // 数据成员
    Expr __expr;


// 以下均为友元授权

// operator+
template <typename T_, int N_, typename LExpr, typename RExpr>
friend inline __Expressionoperator+(
    const __Expression &lhs, const __Expression &rhs);


template <typename T_, int N_, typename LExpr>
friend inline __Expression>> operator+(
    const __Expression &lhs, const T_ &rhs);


template <typename T_, int N_, typename RExpr>
friend inline __Expression, RExpr>> operator+(
    const T_ &lhs, const __Expression &rhs);


// operator-
template <typename T_, int N_, typename LExpr, typename RExpr>
friend inline __Expressionoperator-(
    const __Expression &lhs, const __Expression &rhs);


template <typename T_, int N_, typename LExpr>
friend inline __Expression>> operator-(
    const __Expression &lhs, const T_ &rhs);


template <typename T_, int N_, typename RExpr>
friend inline __Expression, RExpr>> operator-(
    const T_ &lhs, const __Expression &rhs);


// operator*
template <typename T_, int N_, typename LExpr, typename RExpr>
friend inline __Expressionoperator*(
    const __Expression &lhs, const __Expression &rhs);


template <typename T_, int N_, typename LExpr>
friend inline __Expression>> operator*(
    const __Expression &lhs, const T_ &rhs);


template <typename T_, int N_, typename RExpr>
friend inline __Expression, RExpr>> operator*(
    const T_ &lhs, const __Expression &rhs);


// operator/
template <typename T_, int N_, typename LExpr, typename RExpr>
friend inline __Expressionoperator/(
    const __Expression &lhs, const __Expression &rhs);


template <typename T_, int N_, typename LExpr>
friend inline __Expression>> operator/(
    const __Expression &lhs, const T_ &rhs);


template <typename T_, int N_, typename RExpr>
friend inline __Expression, RExpr>> operator/(
    const T_ &lhs, const __Expression &rhs);


// operator%
template <typename T_, int N_, typename LExpr, typename RExpr>
friend inline __Expressionoperator%(
    const __Expression &lhs, const __Expression &rhs);


template <typename T_, int N_, typename LExpr>
friend inline __Expression>> operator%(
    const __Expression &lhs, const T_ &rhs);


template <typename T_, int N_, typename RExpr>
friend inline __Expression, RExpr>> operator%(
    const T_ &lhs, const __Expression &rhs);
};


template <typename T, int N, typename Expr>
__Expression::__Expression() = default;


template <typename T, int N, typename Expr>
__Expression::__Expression(const T &val): __expr(val) {}


template <typename T, int N, typename Expr>
__Expression::__Expression(initializer_list initializerList): __expr(initializerList) {}


template <typename T, int N, typename Expr>
__Expression::__Expression(const Expr &expr): __expr(expr) {}


template <typename T, int N, typename Expr>
template <typename RhsExpr>
__Expression &__Expression::operator=(
    const __Expression &rhs)
{
    for (int idx = 0; idx < N; idx++)
    {
        __expr[idx] = rhs[idx];
    }

    return *this;
}


// 运算符重载

// __Expression + __Expression
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator+(
    const __Expression &lhs, const __Expression &rhs)
{
    return __Expression>(
        __Plus(lhs.__expr, rhs.__expr));
}


// __Expression + __Scalar
template <typename T, int N, typename LExpr>
inline __Expression>> operator+(
    const __Expression &lhs, const T &rhs)
{
    return __Expression>>(
        __Plus>(lhs.__expr, __Scalar(rhs)));
}


// __Scalar + __Expression
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator+(
    const T &lhs, const __Expression &rhs)
{
    return __Expression, RExpr>>(
        __Plus, RExpr>(__Scalar(lhs), rhs.__expr));
}


// __Expression - __Expression
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator-(
    const __Expression &lhs, const __Expression &rhs)
{
    return __Expression>(
        __Minus(lhs.__expr, rhs.__expr));
}


// __Expression - __Scalar
template <typename T, int N, typename LExpr>
inline __Expression>> operator-(
    const __Expression &lhs, const T &rhs)
{
    return __Expression>>(
        __Minus>(lhs.__expr, __Scalar(rhs)));
}


// __Scalar - __Expression
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator-(
    const T &lhs, const __Expression &rhs)
{
    return __Expression, RExpr>>(
        __Minus, RExpr>(__Scalar(lhs), rhs.__expr));
}


// __Expression * __Expression
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator*(
    const __Expression &lhs, const __Expression &rhs)
{
    return __Expression>(
        __Multiplies(lhs.__expr, rhs.__expr));
}


// __Expression * __Scalar
template <typename T, int N, typename LExpr>
inline __Expression>> operator*(
    const __Expression &lhs, const T &rhs)
{
    return __Expression>>(
        __Multiplies>(lhs.__expr, __Scalar(rhs)));
}


// __Scalar * __Expression
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator*(
    const T &lhs, const __Expression &rhs)
{
    return __Expression, RExpr>>(
        __Multiplies, RExpr>(__Scalar(lhs), rhs.__expr));
}


// __Expression / __Expression
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator/(
    const __Expression &lhs, const __Expression &rhs)
{
    return __Expression>(
        __Divides(lhs.__expr, rhs.__expr));
}


// __Expression / __Scalar
template <typename T, int N, typename LExpr>
inline __Expression>> operator/(
    const __Expression &lhs, const T &rhs)
{
    return __Expression>>(
        __Divides>(lhs.__expr, __Scalar(rhs)));
}


// __Scalar / __Expression
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator/(
    const T &lhs, const __Expression &rhs)
{
    return __Expression, RExpr>>(
        __Divides, RExpr>(__Scalar(lhs), rhs.__expr));
}


// __Expression % __Expression
template <typename T, int N, typename LExpr, typename RExpr>
inline __Expressionoperator%(
    const __Expression &lhs, const __Expression &rhs)
{
    return __Expression>(
        __Modulus(lhs.__expr, rhs.__expr));
}


// __Expression % __Scalar
template <typename T, int N, typename LExpr>
inline __Expression>> operator%(
    const __Expression &lhs, const T &rhs)
{
    return __Expression>>(
        __Modulus>(lhs.__expr, __Scalar(rhs)));
}


// __Scalar % __Expression
template <typename T, int N, typename RExpr>
inline __Expression, RExpr>> operator%(
    const T &lhs, const __Expression &rhs)
{
    return __Expression, RExpr>>(
        __Modulus, RExpr>(__Scalar(lhs), rhs.__expr));
}


// 适用于__Expression的operator<<重载
template <typename T, int N, typename Expr>
ostream &operator<<(ostream &os, const __Expression &expressionObj)
{
    os << '[';

    if (N)
    {
        os << expressionObj[0];

        for (int idx = 1; idx < N; idx++)
        {
            os << ", " << expressionObj[idx];
        }
    }

    os << ']';

    return os;
}


// 最终供用户使用的Array类
template <typename T, int N>
using Array = __Expression>;


int main()
{
    // 默认构造函数
    Array<int3> sampleArrayA;  // [?, ?, ?]

    // Fill构造函数
    Array<int3> sampleArrayB(0);  // [0, 0, 0]

    // initializer_list构造函数
    Array<int3> sampleArrayC {123};  // [1, 2, 3]

    // 拷贝构造函数
    Array<int3> sampleArrayD(sampleArrayC);  // [1, 2, 3]

    // 四则运算
    Array<int3> arrayA {123}, arrayB {456}, resArray;

    // operator<<
    cout << arrayA << endl;    // [1, 2, 3]
    cout << arrayB << endl;    // [4, 5, 6]
    cout << resArray << endl;  // [?, ?, ?]

    // 惰性计算
    arrayA + arrayB;  // 什么都不做!
    cout << arrayA + arrayB << endl;  // [5, 7, 9]

    // operator+
    resArray = 2 + arrayA + arrayB + 2;
    cout << resArray << endl;  // [9, 11, 13]

    // operator-
    resArray = 2 - arrayA - arrayB - 2;
    cout << resArray << endl;  // [-5, -7, -9]

    // operator*
    resArray = 2 * arrayA * arrayB * 2;
    cout << resArray << endl;  // [16, 40, 72]

    // operator/
    resArray = 200 / arrayB / arrayA / 2;
    cout << resArray << endl;  // [25, 10, 5]

    // operator%
    resArray = 17 % arrayA % arrayB % 5;
    cout << resArray << endl;  // [0, 1, 2]
}

至此,表达式模板的实现也就全部完成了。

7.8 本章后记

表达式模板,作为一种服务于高性能计算场合的模板技术,被广泛应用于各种线性代数库中(如著名的Eigen库)。表达式模板的精彩之处在于:其充分利用了多级模板抽象所带来的更大的抽象能力,将表达式模板中产生的重重复杂类型完全隐藏于代码实现中,使得用户既能够像书写普通表达式那样进行公式的编码,亦能够享受到表达式模板所带来的极佳效率。模板在高性能计算领域的这一应用,既为模板技术再添精彩一笔,也为我们的故事画上了句号...

8 模板是黑魔法吗?——后记

模板,最早于上世纪90年代被引入至C++,此后的多年内,模板技术迅速发展,促使了大量与之相关的程序设计技术的出现与成熟,并直接导致了STL的出现。在模板出现的几年后,一份“通过报错信息计算质数”的程序代码彻底刷新了人们对于模板的认知,这直接导致了“模板元编程”这一概念的出现(本文作者原想以此份代码的解读作为后记前的最后一章,但这份代码已年代久远,已经不能在作者的GCC编译器上得到理想的效果了,故抱憾作罢)。C++98标准的确立,催生了包括《C++ Templates: The Complete Guide》、《Modern C++ Design》等大量优秀著作的产生。正如《Modern C++ Design》一书的中文译序中所言,这些书籍所讲述的技术,使得我们不再认为模板只是一位“戴上了新帽子”的旧朋友。阅读这些书籍,一定能让你对模板这一技术具有更深入,更全面的认知。

模板是黑魔法吗?类似的问题还有很多(例如:Python的元类是黑魔法吗?)。如果你是一个狂热的模板爱好者,你一定会回答:不!模板是很有用的工具!而如果你对模板不是很感兴趣,或仅仅是因为在学习模板的过程中感到吃力,也许你会对模板的实用性存疑。人对某一个学术领域,某一项技术的认知,必将随着学识、心态、技术本身的兴衰等因素的变化而不断的发生着变化。所以这个问题地答案,也就等着读者你自己去不断的回答了。

注:本文中的部分程序已完整实现于本文作者的Github上,列举如下:

  1. 编译期分数:https://github.com/yingyulou/Fraction
  2. print函数:https://github.com/yingyulou/pprint
  3. Tuple:https://github.com/yingyulou/Tuple
  4. 表达式模板:https://github.com/yingyulou/ExprTmpl


本篇完,查看上篇

作者:樱雨楼,毕业于生物信息学专业,是一枚 Python/C++/Perl 开发,R 语言黑粉,Github 勾搭  https://github.com/yingyulou

猫注:本文所有赞赏,归樱雨楼小姐姐所有,鼓励优质原创,支持好文章!

扫码赞赏她

樱雨楼原创文章索引:

当谈论迭代器时,我谈些什么?

对比 C++ 和 Python,谈谈指针与引用

如何攻克 C++ 中复杂的类型转换?

详解 C++ 的隐式类型转换与函数重载!

谁说 C++ 的强制类型转换很难懂?

浏览 3
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报