下面我们实现一个能够判断两个类型是否相等的元函数:
template<typename T, typename U>
struct IsEqual
{
enum {Result = false};
};
template<typename T>
struct IsEqual<T, T>
{
enum {Result = true};
};
上面的实现中使用了模式特化,当两个类型相等时,选择特化版本,否则选择非特化版本。
接下来我们实现一个在编译期判断两个整数是否相等的元函数:
template<int N, int M>
struct IsNumEqual
{
enum {Result = false};
};
template<int N>
struct IsNumEqual<N, N>
{
enum {Result = true};
};
我们看到,判断整数是否相等的元函数的实现和前面对类型进行判断的元函数的实现完全一样,唯独入参类型不同。由于元函数不支持函数重载,所以必须得为这两个元函数起不同的函数名称。
另外,我们之前一直用Result表示返回类型,现在又用它返回数值。这种返回型别上的差异,会让我们在函数组合的时候碰到不少问题。
如果我们能有一个方法,让所有的元函数的入参的型别和返回值型别都能一致化,那将对元函数的复用和组合带来极大的好处。
有一个办法可以让我们把数值也变成一个类型。
// “tlp/int/IntType.h”
template<int V>
struct IntType
{
enum { Value = V };
using Result = IntType<V>;
};
有了IntType,我们可以让每个整形数字都成为不同的类型,例如IntType<3>
和IntType<4>
就是不同的类型。虽然可以使用IntType<3>::Value
获得它的数值,但在模板元编程中我们更倾向直接使用这种对数值封装过后的类型,因为这样可以让模板元编程的计算对象统一成一种:只有类型!
同理,对于bool值,也如此封装:
// “tlp/bool/BoolType.h”
template<bool V> struct BoolType;
template<>
struct BoolType<true>
{
enum { Value = true };
using Result = BoolType<true>;
};
template<>
struct BoolType<false>
{
enum { Value = false };
using Result = BoolType<false>;
};
using TrueType = BoolType<true>;
using FalseType = BoolType<false>;
用了该技术,后续所有元函数的入参和返回值都只面向类型,再没有数值。如此归一化后,将会从根本上避免由于元编程的操作对象不一致造成的写法上的重复,进一步让元函数的组合能力变得更强。
得益于上述统一,IsEqual只需要如下一份实现就够了:
// “tlp/bool/algo/IsEqual.h”
template<typename T, typename U>
struct IsEqual
{
using Result = FalseType;
};
template<typename T>
struct IsEqual<T, T>
{
using Result = TrueType;
};
前面我们实现过一个Print模板,用于在编译过程中打印常量的值。现在一切都是类型,我们对Print的实现进行修改,让其可以在编译期打印出类型结果的值。
// “tlp/test/details/Print.h”
template <typename T>
struct Print
{
const int Value = 1 / (sizeof(T) - sizeof(T));
};
#define __print(...) Print<__VA_ARGS__> UNIQUE_NAME(tlp_print_)
现在Print的入参变为一个类型T,使用__print(T)
会产生一个编译器的告警,在告警信息中输出T的具体类型。__print()
在输出前会先对其参数进行求值。
// TestPrint.cpp
__print(IsEqual<IntType<5>, IntType<6>>::Result);
__print(IsEqual<TrueType, BoolType<true>>::Result);
上面的cpp文件在我的编译环境下输出的告警信息中包含如下内容,其中携带着对__print()
的参数的求值结果。
TestPrint.cpp : in instantiation of default member initializer Print<BoolType<false> >::Value' required here
...
TestPrint.cpp : in instantiation of default member initializer Print<BoolType<true> >::Value' required here
可见IsEqual<IntType<5>, IntType<6>>::Result
的值是BoolType<false>
,而IsEqual<TrueType, BoolType<true>>::Result
的值是BoolType<true>
。
得益于我们将元编程的计算统一到类型上,我们得到了一个统一的Print元函数。