C++模板元编程---lambda表达式简单实现

silverfish 发布于6月前 阅读12110次
0 条评论

C++模板元编程---lambda表达式简单实现

为什么封面这么丑?后面再说。先写一些基本概念吧,包括什么是元函数,什么是元函数类,以及元编程中lambda表达式是什么鬼。

元函数 (metafunction):

//为了方便起见,假设 tyPlus 一定返回 int
template<class T1, class T2>
struct tyPlus {
	using type = tyPlus<T1, T2>;
	const static int value = (T1::type::value + T2::type::value);
};

这里,tyPlus是一个简单的对两个类型求和的元函数,为什么叫它函数呢?因为他是这么用的:

//_int是常量模板,用于包装一个整型值
template<int val>
struct _int {
	using type = _int<val>;
	const static int value = val;
	operator int() {
		return value;
	}
};

cout << tyPlus<_int<2>, _int<3>>::type::value << endl; //输出5

我们可以看到,在元编程中,tyPlus和运行期的函数具有相同的形式,tyPlus<x,y>我们可以类比做func(x,y)的形式,即模板参数等价与函数形参,模板返回类型为tyPlus<x,y>::type,其返回的类型的值为tyPlus<x,y>::type::value。因为这样的规范,我们把这样的模板类叫做元函数,后面提到的元函数都具有以下性质:

1. 接受 >=0 个模板参数的模板类。

2. 内部定义了类型 "type" 作为该元函数返回的类型。

而 type 作为返回类型,有没有 value 值,这取决于具体情况。

元函数类(metafunction class):

元函数类其实很简单,就是对元函数的一层封装而已,把上面的 tyPlus 包装成元函数类以后如下:

//直接在网页敲的,不知道是否有bug
struct tyPlus_f{
    template<class T1, class T2>
    struct apply{
	using type = tyPlus<T1, T2>;
	const static int value = (T1::type::value + T2::type::value);
    };
};

元函数类遵循以下规范:

1. 本身是个非模板类。

2. 内部定义元函数 apply,用于真实的操作。

其实有些类似 C++ 通过重载()实现的可调用对象,只不过方式不同。

另外,在已经有了 tyPlus 元函数的情况下,也可以通过 元函数转发 来实现元函数类:

struct tyPlus_f{
    template<class T1, class T2>
    struct apply : tyPlus<T1, T2> {};
};

Lambda表达式:

其实lambda表达式本身指的是匿名函数,在C++中,lambda表达式是一个诸如:

auto lambda_func = [=](int x, int y){return x + y;};

的东西,也就是匿名函数,而如果我们通过 auto 获取这个类型能得到什么呢?其实编译器会把后面一串处理成一个类似以下的类的 实例

class lambda_1hkh1k235g12k34hklj123hl44gjk435hg6{
    operator()(int x, int y){
        return x + y;
    }
};

而元编程中的 lambda 跟上面的共同点在于,他返回一个元函数类,而不同点在于本身lambda函数的功能是由其他元函数构建而成,并非直接写一串代码定义功能。但它的亮点在于支持模板参数的占位符。这允许我们写出这样的代码:

//记住lambda返回的是元函数类,真实功能在类的apply元函数中。
using tyDouble = lambda<tyPlus<_1, _1>>::type;
cout << tyDouble::apply<_int<20>> << endl;   //40

而当传入不含占位符的任何类型(包括非模板类型)都会直接在type中返回原样,比如:

lambda<tyPlus_f>::type

tyPlus_f

是一样的类型。

好,基础知识介绍完了,开始实现lambda模板类。

首先,实现底层的 lambdaImpl 类。

template<class T, class ...trueArgs>
struct lambdaImpl {
	using type = T;
};

当参数不是模板时,直接返回该参数类型T。后面那个变长模板参数 trueArgs 后面讲。

然后实现占位符的类

template<unsigned int N>
struct arg {
	const static unsigned int value = N;
};

没错,占位符就是这么个破玩意儿,value也就是N记录了该占位符表示第N个参数。

继续写 lambdaImpl 的偏特化。

当给lambdaImpl传入一个模板时,这样处理

template<
    template<class ...Args> class T,
    class ...Args
>
struct lambdaImpl<T<Args...>>{
    struct type {
        template<class ...trueArgs>
        struct apply : T < typename lambdaImpl<Args, trueArgs...>::type... >{};
    };
};

因为用户只会调用apply一次,并在apply中传入真实参数,所以第一次处理的时候再往下递归必须把trueArgs...一起传进去,这也就是前面那个普通情况下的 lambdaImpl 为什么要带着一个class ...trueArgs的原因。

以下偏特化内容。。。我讲不动了,总之就是用来处理嵌套模板类的,就这么几行代码,应该很好懂,嗯:

template<
    template<class ...Args> class T,
    class ...Args,
    class ...trueArgs
>
struct lambdaImpl<T<Args...>, trueArgs...> {
    using type = typename lambdaImpl<T<Args...>>::type::template apply<trueArgs...>::type;
  };

然后,递归遇到占位符时,对占位符进行处理:

template<
	unsigned int N,
	class ...trueArgs
>
struct lambdaImpl<arg<N>, trueArgs...>{
	using type = typename getArg<N, trueArgs...>::type;
};

实现根据N,返回第N个参数的功能,即 getArg

template<unsigned int N, class firstArg, class ...trueArgs>
struct getArg {
	using type = typename getArg<N - 1, trueArgs...>::type;
};

template<class firstArg, class ...Args>
struct getArg<1, firstArg, Args...> {
	using type = firstArg;
};

最后留个接口,定义一组占位符

template<class T>
struct lambda {
	using type = typename lambdaImpl<T>::type;
};

namespace placeholders {
	typedef arg<1> _1;
	typedef arg<2> _2;
	typedef arg<3> _3;
	typedef arg<4> _4;
	typedef arg<5> _5;
	typedef arg<6> _6;
	typedef arg<7> _7;
	typedef arg<8> _8;
	typedef arg<9> _9;
	typedef arg<10> _10;
}

占位符这东西,你想定义几个都行。。。或者直接用 arg<N> 也是可以的。

全部代码很少,随便扔哪个头文件:

#pragma once
namespace tymeta {
	template<unsigned int N>
	struct arg {
		const static unsigned int value = N;
	};

	template<unsigned int N, class firstArg, class ...trueArgs>
	struct getArg {
		using type = typename getArg<N - 1, trueArgs...>::type;
	};

	template<class firstArg, class ...Args>
	struct getArg<1, firstArg, Args...> {
		using type = firstArg;
	};

	template<class T, class ...trueArgs>
	struct lambdaImpl {
		using type = T;
	};

	template<
		unsigned int N,
		class ...trueArgs
	>
	struct lambdaImpl<arg<N>, trueArgs...>{
		using type = typename getArg<N, trueArgs...>::type;
	};

	template<
		template<class ...Args> class T,
		class ...Args,
		class ...trueArgs
	>
	struct lambdaImpl<T<Args...>, trueArgs...> {
		using type = typename lambdaImpl<T<Args...>>::type::template apply<trueArgs...>::type;
	};
		

	template<
		template<class ...Args> class T,
		class ...Args
	>
	struct lambdaImpl<T<Args...>>{
		struct type {
			template<class ...trueArgs>
			struct apply : T < typename lambdaImpl<Args, trueArgs...>::type... >{
				
			};
		};
	};

	template<class T>
	struct lambda {
		using type = typename lambdaImpl<T>::type;
	};

	namespace placeholders {
		typedef arg<1> _1;
		typedef arg<2> _2;
		typedef arg<3> _3;
		typedef arg<4> _4;
		typedef arg<5> _5;
		typedef arg<6> _6;
		typedef arg<7> _7;
		typedef arg<8> _8;
		typedef arg<9> _9;
		typedef arg<10> _10;
	}
}

最后,按惯例,来测试一下:

我先写一组基础设施:

template<class T1, class T2>
struct tyPlus {
	using type = tyPlus<T1, T2>;
	const static int value = (T1::type::value + T2::type::value);
};

template<class T1, class T2>
struct tyMinus{
	using type = tyMinus<T1, T2>;
	const static int value = (T1::type::value - T2::type::value);
};

template<class T1, class T2>
struct tyMultiple{
	using type = tyMultiple<T1, T2>;
	const static int value = (T1::type::value * T2::type::value);
};

template<class T>
struct tyInverse {
	using type = tyInverse<T>;
	const static int value = -T::type::value;
};

template<int val>
struct _int {
	using type = _int<val>;
	const static int value = val;
	operator int() {
		return value;
	}
};

为简单起见,所有元函数均默认处理 _int 型数据,所以 value 均定义为了 int。

前面的 tyDouble

using tyDouble = tymeta::lambda<tyPlus<tymeta::placeholders::_1, tymeta::placeholders::_1>>::type;
cout << tyDouble::apply<_int<21>>::type::value << endl;

输出42。

写个(x + y) * (x - y) 也 so easy,因为我们含有占位符的模板类可以嵌套:

using _1 = tymeta::placeholders::_1;
using _2 = tymeta::placeholders::_2;
using xy = tymeta::lambda<tyMultiple<tyPlus<_1, _2>, tyMinus<_1, _2>>>::type;
cout << xy::apply<_int<25>, _int<20>>::type::value << endl;

计算 (25 + 20) * (25 - 20)

输出 225,后面我们默认 _1, _2 已经using过了。

我们试试直接实现减法而不用 tyMinus?

using lambdaMinus = tymeta::lambda<tyPlus<_1, tyInverse<_2>>>::type;
cout << lambdaMinus::apply<_int<25>, _int<20>>::type::value << endl;

输出5。

看起来很厉害。

至于为什么封面这么丑,因为我觉得 metaprogramming 写出来的代码和这棵树气质上差不多。

终,继续刷妖刀姬去了。

查看原文:C++模板元编程---lambda表达式简单实现

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。