dlang模板和模板化类,结构和函数之间的区别

时间:2018-01-06 11:24:12

标签: templates d

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

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

2 个答案:

答案 0 :(得分:10)

当您看到template bar(T)时,您可以将其视为命名空间 - 有点像结构或类。就像struct Foo(T)一样,内容当然是模板参数的模板,通常只能通过bar!T.memberName访问。

我说一般,因为有一些特殊的规则。首先,我们有eponymous templates。如果template foo(T)的成员与模板名称完全相同,则foo!Tfoo!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.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;
}

我希望这有一些帮助,请问是否有任何不清楚的地方。 :)

答案 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.traitsstd.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。