我在理解D中的模板时遇到了一些麻烦。
我理解struct Foo(T) { }
或类或函数的等价物是什么,但什么是template Bar(T) { }
?它与类,结构或函数模板有何不同,何时使用?
答案 0 :(得分:10)
当您看到template bar(T)
时,您可以将其视为命名空间 - 有点像结构或类。就像struct Foo(T)
一样,内容当然是模板参数的模板,通常只能通过bar!T.memberName
访问。
我说一般,因为有一些特殊的规则。首先,我们有eponymous templates。如果template foo(T)
的成员与模板名称完全相同,则foo!T
是foo!T.foo
的同义词,整个命名空间的想法sorta消失。 foo!T
内的其他成员隐藏且无法访问。实际上,当您编写struct Foo(T) {}
时,编译器会将其转换为template Foo(T) { struct Foo {} }
。
另一个特殊情况是mixin templates,它们基本上是在实例化时将逐字删除的片段。也就是说,此代码(注意mixin
之前的template
关键字):
mixin template Foo() {
int i;
}
struct Bar {
mixin Foo!();
}
在功能上等同于此代码:
struct Bar {
int i;
}
现在,你为什么只使用template
?第一个是模板元编程。如果您查看std.traits
或std.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;
}
我希望这有一些帮助,请问是否有任何不清楚的地方。 :)
答案 1 :(得分:2)
嗯,技术上,
struct S(T)
{
...
}
相当于
template S(T)
{
struct S
{
...
}
}
和
auto foo(T)(T t)
{
...
}
相当于
template foo(T)
{
auto foo(T t)
{
...
}
}
只是提供了较短的语法,以便为常见用例提供更清晰的功能。 template
为代码生成创建了一个模板。在使用参数实例化模板之前,该模板中的任何内容都不会作为实际代码存在,并且生成的代码取决于模板参数。因此,模板的语义分析直到它被实例化后才会进行。
结构,类和函数发生的部分原因是模板作为其声明的一部分,而不是明确地声明模板来包装它们,这就是所谓的同名模板。当使用模板时,任何模板中具有与模板同名的符号都将替换为该符号。 e.g。
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
。 e.g。
template count(Args...)
{
enum count = Args.length;
}
或
template ArrayOf(T)
{
alias ArrayOf = T[];
}
还有同名模板的快捷语法,如果用户没有任何其他成员,则这些模板不是用户定义的类型或功能。 e.g。
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[])));
这在模板约束(或模板约束中使用的其他同名模板)中非常有用,或者它可以与静态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
中并使用各种同名模板通常更有效。例如,多年前,Phobos曾经拥有同名模板Format
,它在编译时用于生成字符串,类似于std.format.format在运行时的方式,但是一旦CTFE被改进到format
{1}}可以在编译时使用,使用Format
代替format
没有任何意义,因为Format
在比较中非常慢(执行大量递归模板实例化)可以变得昂贵)。因此,在操作类型时仍然需要使用模板元编程,但如果你可以做你需要用CTFE做的事情,那通常会更好。
如果您正在寻找Phobos中的元编程工具,std.traits和std.meta是主要观察的地方,尽管有些根据他们的意图分散在整个Phobos中(例如,范围特定的那些在std.range.primitives)。
此外,类似于如何混合字符串,您可以混合模板。例如
template foo(T)
{
T i;
}
void main()
{
mixin foo!int;
auto a = i;
}
因此,模板生成的代码实际上会在您混合的位置进行复制粘贴。或者,您可以将mixin
放在模板声明中,以便将其用作任何内容不是混合物。 e.g。
mixin template foo(T)
{
T i;
}
就个人而言,我通常只是使用字符串mixins来做这类事情,但有些人更喜欢模板mixins。