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

Difference between dlang templates and templated classes, structs and functions

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

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

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

访问

我说的是一般情况,因为有一些特殊规定。首先,我们有 eponymous templates。如果 template foo(T) 有一个与模板同名的成员,那么 foo!T 就是 foo!T.foo 的同义词,整个命名空间的概念就这样消失了。 foo!T 中的其他成员已隐藏且无法访问。事实上,当你写 struct Foo(T) {} 时,编译器会把它变成 template Foo(T) { struct Foo {} }.

另一个特殊情况是 mixin templates,它们基本上是在您实例化它们时将逐字删除的片段。即这段代码(注意template前的mixin关键字):

mixin template Foo() {
    int i;
}

struct Bar {
    mixin Foo!();
}

在功能上等同于此代码:

struct Bar {
    int i;
}

现在,您为什么只使用 template?第一个是模板元编程。如果您查看 std.traits or std.meta,这些模块中充满了模板。不是 mixin 模板,不是模板化结构、classes 或函数,而只是模板。它们对传递给它们的值和类型进行操作,returns 某种值或类型。

像这样使用的模板的一个非常简单的例子是 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 and using various eponymous templates on them. For instance, years ago, Phobos used to have the eponymous template Format which was used to generate strings at compile time similarly to how std.format.format 中更有效,在运行时这样做,但是一旦 CTFE 改进到 format 可以在编译时使用,使用 Format 而不是 format 是没有意义的,因为相比之下 Format 非常慢(做很多递归模板实例化可以得到贵)。因此,在对类型进行操作时仍然需要使用模板元编程,但是如果您可以使用 CTFE 完成您需要做的事情,那通常会更好。

如果您正在寻找 Phobos 中的元编程工具,std.traits and std.meta are the main place to look, though some are scattered throughout Phobos depending on what they're for (e.g. the range-specific ones are in std.range.primitives)。

此外,与混合字符串的方式类似,您可以混合模板。例如

template foo(T)
{
    T i;
}

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

因此,模板生成的代码基本上会被复制粘贴到您混合使用的地方。您可以选择将 mixin 放在模板声明上,以禁止将其用作除一个混音。例如

mixin template foo(T)
{
    T i;
}

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