两年前,笔者因为项目原因刚开始接触C++,当时就在想,如果C++有类似C#中的泛型限定就好了,能让代码简单许多。我也一度认为:
虽然C++有模板类,但是却没办法实现C#中泛型特有的 where
关键词:
public class Parent { /* ... */ }
public class Child : Parent { /* ... */ }
public class AnotherClass { /* ... */ }
public class GenericClass<T> where T : Parent
{
public static void DoSomething(T item) { }
}
static void Main(string[] args)
{
Child c = new Child();
AnotherClass a = new AnotherClass();
GenericClass<Parent>.DoSomething(c);
GenericClass<Child>.DoSomething(c);
GenericClass<AnotherClass>.DoSomething(); // --- 报错
}
但实际上,C++通过不仅能做到这部分内容,而且比C#能做的还要更多。而且很多内容甚至是可以在Compile Time就能做到限制的 —— 不用等到代码跑起来,编译的时候就能够告诉你代码哪里有问题。
C++实现这部分功能所涉及到的技术就是 Metaprogramming,直译过来是“元编程”。
我相信 元编程 这个名字对于大多数人来说是没有意义的,就好像我们第一次听到某种深海动物名字一样,它叫什么不重要,重要的是,它是什么。
比如我现在向大家介绍一种深海动物,“须蛸”。如果我不放这个图,“须蛸”这个名字就是没有任何意义的 (放了这图似乎也没多大意义,但是至少你更加信服这个名字不是我瞎编的了)。
所以什么是元编程?
元编程是对代码的编程
元编程英文里的Meta可以认为是“超级”的意思,这里的“超级”应该不是指“能力强过、高过”,亦不是说“凌驾于xxx之上”的意思。
在我个人理解里,Metaprogramming技术本身并非高明于我们平时的普通编程技术,它只是思维方式不一样。但不幸的是,C++的Metaprogramming是一只披着C++语言外皮的“狼”(需要完全用另一套思维逻辑去思考/实现的另一个编程领域),所以笔者认为这也是为什么许多C++用了许多年的人谈到Metaprogramming这个领域时仍噤若寒蝉——因为它们压根不是一个东西,只是恰好语法底层用到的是同一套东西而已——而且在实际工作中你可以完全不使用该内容而写好自己的代码(只不过在某些情况可能要多付出一点体力劳动……不过有Vim在,那也不是什么大问题嘛)。
说到Meta的“超级”的含义,Metaprogramming在我这里十分像优化领域里的Hyperparameter Optimization (超参数优化)里的“Hyper” —— 同样是“超级”的意思,同样的,超参数优化也不是说它本身相较于普通的参数优化有什么更深奥的技术,只不过是Optimization的参数的Optimization,优化参数的优化,所以就用差不多的技术在对不同的目标套了一层。
巧了么不是,Metaprogramming是对普通programming的programming,我觉得起名叫Hyperprogramming也是挺不错的。而且他俩都有一个共同点,那就是特别烧脑,都要在原有的概念上嵌套一层。
好了,说了这么多,那倒底啥是Metaprogramming?下面就举个简单的例子,我们用C#来实现它,简单说说啥是Metaprogramming。
什么是Metaprogramming
现在我们要写个程序,需求是:
能够返回正整数1-5的平方。
普通编程的方法是:
int ReturnSquare(int i) => i * i;
Metaprogramming的方法是:
写一个Console程序,输出一个cs文件:
static void Main(string[] args)
{
using var fs = File.Create("d:\square.cs", FileMode.Create);
using var sr = new StreamWriter(fs);
sr.AppendLine("int ReturnSquare(int i) => i * i");
sr.Flush();
}
这就是Metaprogramming。
???
是的,这就是Metaprogramming。
不过这只是最广义上的概念,但这个概念的确如此 —— 用代码去写代码。这个技术就是Metaprogramming。
我们一般说C++的Metaprogramming是指 “使用C++的模版技术来借助编译器在编译时帮我们生成、检查代码”。
笔者这里再插一段,C#也是有模版的,也是需要借助编译器才可以,感兴趣的读者可以自行搜索“C# Text Template”,或者官方链接:
https://learn.microsoft.com/en-us/visualstudio/modeling/code-generation-and-t4-text-templates?view=vs-2022
对于Metaprogarmming的介绍今天就先到这里了,后面有想到的会继续补充,下面的时间留给标题里的内容。
我是被标题吸引的,我要C++实现泛型限定
- 限定
Parent
类
#include <type_traits>
class Parent{};
class Child : public Parent{};
class AnotherClass {};
namespace hidden {
template <bool b, typename T>
struct Helper
{
static void DoSomething(T item) = delete;
};
template <typename T>
struct Helper<true, T>
{
static void DoSomething(T item)
{
// ...
}
};
}
template <typename T>
using Generic = hidden::Helper<std::is_base_of_v<Parent, T>, T>;
int main()
{
Child c;
AnotherClass a;
Generic<Parent>::DoSomething(c);
Generic<AnotherClass>::DoSomething(a); // -- 报错
}
- 限定 “类里必须要有Foo成员函数” 才可使用
// 具备成员函数Foo
class HasFoo
{
public:
int Foo();
};
// 不具备成员函数Foo,仅有静态函数Foo
class NoFoo
{
public:
static int Foo();
};
namespace hidden {
template <typename T>
std::true_type Call(std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::Foo)>, void*>);
template <typename T>
std::false_type Call(...);
};
template <typename T>
using has_member_function_foo = decltype(hidden::Call<T>(nullptr));
int main()
{
static_assert( // --- passed
has_member_function_foo<HasFoo>::value,
"does not have a member of Foo");
static_assert( // --- failed 编译报错
has_member_function_foo<NoFoo>::value,
"does not have a member of Foo");
}
好了我们下次再见。🦀