在 D 中帮助模板元编程的两个最大因素是模板约束和 static if - C++ 理论上可以添加这两者,这将大大受益。
模板约束允许您在模板上设置一个条件,该条件必须为真,模板才能被实例化。例如,这是std.algorithm.find 的重载之一的签名:
R find(alias pred = "a == b", R, E)(R haystack, E needle)
if (isInputRange!R &&
is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
为了能够实例化这个模板化函数,R 类型必须是std.range.isInputRange 定义的输入范围(所以isInputRange!R 必须是true),并且给定的谓词需要是一个二进制函数,它使用给定的参数编译并返回一个可隐式转换为bool 的类型。如果模板约束条件的结果是false,那么模板将不会编译。当模板无法使用给定的参数进行编译时,这不仅可以保护您免受在 C++ 中遇到的令人讨厌的模板错误,而且还可以使您可以根据模板约束重载模板。例如,find 的另一个重载是
R1 find(alias pred = "a == b", R1, R2)(R1 haystack, R2 needle)
if (isForwardRange!R1 && isForwardRange!R2
&& is(typeof(binaryFun!pred(haystack.front, needle.front)) : bool)
&& !isRandomAccessRange!R1)
它采用完全相同的参数,但它的约束不同。因此,不同类型使用相同模板化函数的不同重载,find 的最佳实现可以用于每种类型。没有办法在 C++ 中干净利落地做这种事情。稍微熟悉典型模板约束中使用的函数和模板后,D 中的模板约束相当容易阅读,而您需要在 C++ 中进行一些非常复杂的模板元编程才能尝试这样的事情,而普通程序员不会自己也能看懂,更别说自己动手了。 Boost就是一个很好的例子。它做了一些令人惊奇的事情,但它非常复杂。
static if 进一步改善了这种情况。就像模板约束一样,任何可以在编译时评估的条件都可以与它一起使用。例如
static if(isIntegral!T)
{
//...
}
else static if(isFloatingPoint!T)
{
//...
}
else static if(isSomeString!T)
{
//...
}
else static if(isDynamicArray!T)
{
//...
}
else
{
//...
}
编译到哪个分支取决于哪个条件首先评估为true。因此,在模板中,您可以基于模板实例化的类型或基于编译时可以评估的任何其他类型来专门化其实现部分。例如,core.time 使用
static if(is(typeof(clock_gettime)))
根据系统是否提供clock_gettime来不同地编译代码(如果有clock_gettime,则使用它,否则使用gettimeofday)。
我见过的 D 改进模板的最明显的例子可能是我的团队在 C++ 中遇到的一个问题。我们需要根据给定的类型是否从特定的基类派生来以不同的方式实例化模板。我们最终使用了基于this stack overflow question 的解决方案。它可以工作,但仅测试一种类型是否源自另一种类型是相当复杂的。
然而,在 D 中,您所要做的就是使用 : 运算符。例如
auto func(T : U)(T val) {...}
如果T 可以隐式转换为U(就像T 派生自U 一样),那么func 将编译,而如果T 不能隐式转换为@ 987654350@,那就不行了。 这种简单的改进甚至使基本的模板专业化变得更加强大(即使没有模板约束或static if)。
就我个人而言,除了容器和<algorithm> 中的偶尔函数之外,我很少在 C++ 中使用模板,因为它们使用起来非常痛苦。它们会导致丑陋的错误,并且很难做任何花哨的事情。做任何有点复杂的事情,您都需要非常熟练地使用模板和模板元编程。虽然使用 D 中的模板,但我一直使用它们非常简单。这些错误更容易理解和处理(尽管它们仍然比通常使用非模板函数的错误更糟糕),而且我不必弄清楚如何通过花哨的元编程来强制语言做我想做的事情.
没有理由 C++ 不能获得 D 所具有的大部分能力(如果他们能够整理出这些能力,C++ 概念会有所帮助),但直到他们添加基本的条件编译以及类似于模板约束和static if 的构造对 C++ 而言,C++ 模板在易用性和功能方面无法与 D 模板相比。