d - dlang模板和模板化类、结构和函数之间的区别



我在理解 D 中的模板时遇到了一些麻烦。

我了解类或函数的struct Foo(T) { }或等效项的作用,但是什么是template Bar(T) { }? 它与类、结构或函数模板有何不同,何时使用?

当你看到template bar(T)时,你可以把它想象成一个命名空间 - 有点像一个结构或类。就像struct Foo(T)一样,内容当然是在模板参数上模板化的,并且通常只能通过bar!T.memberName访问。

我说一般,因为有一些特殊的规则。首先,我们有同名模板。如果template foo(T)有一个与模板同名的成员,那么foo!T就是foo!T.foo的同义词,整个命名空间的想法就会消失。foo!T内的其他成员是隐藏的,无法访问。实际上,当你编写struct Foo(T) {}时,编译器会将其转换为template Foo(T) { struct Foo {} }

另一种特殊情况是 mixin 模板,它们基本上是当您实例化它们时将逐字删除的片段。也就是说,这段代码(注意template前面的mixin关键字):

mixin template Foo() {
int i;
}
struct Bar {
mixin Foo!();
}

在功能上等效于以下代码:

struct Bar {
int i;
}

现在,你为什么要只使用template?首先是模板元编程。如果您查看std.traitsstd.meta,这些模块都充满了模板。不是mixin模板,不是模板化的结构,类或函数,而只是模板。它们对传递给它们的值和类型进行操作,并返回某种值或类型。

像这样使用的模板的一个非常简单的例子是std.meta.Reverse.它获取参数列表并反转它们,如下所示:

template Reverse(TList...)
{
static if (TList.length <= 1)
{
alias Reverse = TList;
}
else
{
alias Reverse =
AliasSeq!(
Reverse!(TList[$/2 ..  $ ]),
Reverse!(TList[ 0  .. $/2]));
}
}

想要使用模板的另一种情况是,在某些情况下应省略模板化类型。假设你正在制作自己的Nullable(T),并且您希望Nullable!(Nullable!T)永远Nullable!T。如果您只是编写struct Nullable(T) {},则不会获得此行为,并且最终会得到双可为空的类型。解决方案是使用模板约束:

struct Nullable(T) if (!isNullable!T) {}

以及处理退化情况的模板:

template Nullable(T) if (isNullable!T) {
alias Nullable = T;
}

我希望这有所帮助,如果有什么不清楚的地方,请问。 :)

嗯,从技术上讲,

struct S(T)
{
...
}

相当于

template S(T)
{
struct S
{
...
}
}

auto foo(T)(T t)
{
...
}

相当于

template foo(T)
{
auto foo(T t)
{
...
}
}

只是提供了较短的语法,以使常见用例的内容更清晰。template创建用于代码生成的模板。在使用参数实例化模板之前,该模板中的任何内容都不会作为实际代码存在,并且生成的代码取决于模板参数。因此,模板的语义分析要等到实例化后才会完成。

结构、类和函数将模板作为其声明的一部分,而不是显式声明模板来包装它们,这部分情况就是所谓的同名模板。使用模板时,任何包含与模板同名的符号的模板都将替换为该符号。例如

template isInt(T)
{
enum isInt = is(T == int);
}

然后可以在表达式中使用,例如

auto foo = isInt!int;

枚举isInt.isInt的值用于表达式中代替模板。此技术与模板约束的帮助程序模板大量使用。例如isInputRange

auto foo(R)(R range)
if(isInputRange!R)
{...}

isInputRange定义为同名模板,如果给定类型是输入范围,则会导致true,否则false。在某种程度上,这有点像有一个对类型进行操作的函数,尽管它也可以对值进行操作,并且结果不必bool。例如

template count(Args...)
{
enum count = Args.length;
}

template ArrayOf(T)
{
alias ArrayOf = T[];
}

还有一个用于同名模板的快捷方式语法,如果它们没有任何其他成员,则它们不是用户定义的类型或函数。

例如
enum count(Args...) = Args.length;
alias ArrayOf(T) = T[];

正如我所说,同名模板可能有点像具有对类型进行操作的函数,这就是它们在需要对类型执行复杂操作时的作用。例如,将该ArrayOf模板与std.meta.staticMap一起使用,您可以执行以下操作

alias Arrays = staticMap!(ArrayOf, int, float, byte, bool);
static assert(is(Arrays == AliasSeq!(int[], float[], byte[], bool[])));

这在模板约束(或模板约束中使用的其他同名模板)中非常有用,或者可以与 static foreach 之类的东西一起使用以更明确地生成代码。 例如,如果我想测试所有字符串类型的一些代码,我可以写类似的东西

alias Arrays(T) = AliasSeq!(T[],
const(T)[],
const(T[]),
immutable(T)[],
immutable(T[]));
unittest
{
import std.conv : to;
static foreach(S; AliasSeq(Arrays!char, Arrays!wchar, Arrays!dchar))
{{
auto s = to!S("foo");
...
}}
}

这些技术通常在元编程中大量使用。除了类型之外,它们还可以用于值,但通常对值使用 CTFE 更有效,而不是将它们放在AliasSeq中并在其上使用各种同名模板。例如,几年前,火卫一曾经有同名模板Format,用于在编译时生成字符串,类似于 std.format.format 在运行时的方式,但是一旦 CTFE 改进到可以在编译时使用format,使用Format而不是format就没有意义了, 因为相比之下Format速度非常慢(做很多递归模板实例化可能会很昂贵)。因此,在对类型进行操作时仍然需要使用模板元编程,但是如果您可以执行CTFE需要做的事情,那通常更好。

如果你正在Phobos中寻找元编程工具,std.traits和std.meta是主要的地方,尽管有些分散在整个火卫一中,这取决于它们的用途(例如,特定于范围的工具在std.range.primitives中)。

此外,与如何混合字符串类似,您可以混合模板。

template foo(T)
{
T i;
}
void main()
{
mixin foo!int;
auto a = i;
}

因此,模板生成的代码基本上被复制粘贴到您混合它的位置。或者,您可以在模板声明上放置mixin,使其作为 mixin 以外的任何内容使用都是非法的。例如

mixin template foo(T)
{
T i;
}

就个人而言,我通常只使用字符串混音来做这种事情,但有些人更喜欢模板混音。

最新更新