Difference between dlang templates and templates, structures and template functions

I have some problems understanding patterns in D.

I understand what struct Foo(T) { } or the equivalent for a class or function is, but what is template Bar(T) { } ? how does it differ from a class template, structure or function and when will I use it?

+5
source share
2 answers

When you see template bar(T) , you can think of it as a namespace - sort of like a struct or class. Just like struct Foo(T) , the contents of a template template are naturally available for the template and are usually only available through bar!T.memberName .

I speak generally because there are some special rules. Firstly, templates of the same name . If template foo(T) has a member with the same name as the template, then foo!T is synonymous with foo!T.foo , and all sorting of the namespace idea disappears. Other members inside foo!T are hidden and inaccessible. In fact, when you write struct Foo(T) {} , the compiler turns it into template Foo(T) { struct Foo {} } .

Another special case is mixin templates , which are mostly fragments that will be discarded verbatim when created. That is, this code (note the mixin keyword before template ):

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

functionally equivalent to this code:

 struct Bar { int i; } 

Now why use only template ? The first is metaprogramming patterns. If you look at std.traits or std.meta , these modules are populated with templates. Do not mix templates, not template structures, classes or functions, but simply templates. They work with the values ​​and types passed to them and return some value or type.

A very simple example of the template used: std.meta.Reverse . It takes a list of arguments and cancels them and looks like this:

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

Another case when you want to use templates is that your template type needs to be removed in some cases. Say you create your own Nullable(T) and you want Nullable!(Nullable!T) always be Nullable!T If you just wrote struct Nullable(T) {} , you would not get this behavior and you would get double nullable types. The solution is to use a template constraint:

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

and a template for handling a degenerate case:

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

Hope this helps, and ask if something is clear. :)

+9
source

Well, technically,

 struct S(T) { ... } 

equivalently

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

and

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

equivalently

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

It is just that a shorter syntax is provided to make things cleaner for general use. template creates a template for generating code. Nothing in this template exists as real code until the template is created using arguments, and the generated code depends on the template arguments. Thus, semantic analysis for the template is not performed until it is created.

Part of what happens to structures, classes, and functions that have a template as part of their declaration, instead of explicitly declaring a template to carry them, is called the so-called template of the same name. Any template that has a symbol in it with the same name as the template is replaced with this symbol when using the template. eg.

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

can then be used in an expression such as

 auto foo = isInt!int; 

and enum isInt.isInt used in the expression instead of the template. This method is used heavily with helper patterns for pattern constraints. e.g. isInputRange in

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

isInputRange defined as the template of the same name, the result of which is true if this type is an input range and false otherwise. In some ways, this is similar to a function for working with types, although it can also work with values, and the result should not be bool . eg.

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

or

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

There is also shortcut syntax for templates of the same name that are not user-defined types or functions if they have no other members. eg.

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

And, as I said, the template of the same name can be similar to a function for working with a type and that they are used when complex operations need to be performed by type. For example, using this ArrayOf template with std.meta.staticMap , you can do something like

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

This can be extremely useful in template constraints (or other templates of the same name that will be used in template constraints), or it can be used with something like static foreach to more clearly generate code. for example, if I wanted to test some code with all types of strings, I could write something like

 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"); ... }} } 

These methods are usually quite widely used in metaprogramming. They can be used for values ​​in addition to types, but it is usually more efficient to use CTFE for values ​​rather than putting them in AliasSeq and using different templates of the same name on them. For example, many years ago, Phobos had the Format template of the same name, which was used to generate strings at compile time in the same way as std.format.format does at run time, but as soon as CTFE was improved to such an extent that Format could be used at compile time, it made no sense to use Format instead of Format , because Format was terribly slow in comparison (executing many recursive template instances can be expensive). Thus, the use of template metaprogramming is still required when working with types, but if you can do what you need to do with CTFE, this is generally better.

If you are looking for metaprogramming tools in Phobos, std.traits and std.meta are the main place to search, although some of them are scattered throughout Phobos depending on what they are intended for (for example, for specific ranges in std.range.primitives )

Also, just like you can mix strings, you can mix patterns. eg

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

Thus, the code created by the template is essentially copied in the form in which you mix it. Optionally, you can put mixin in a template declaration to make it illegal, to use it as something other than mixin. eg.

 mixin template foo(T) { T i; } 

Personally, I usually use string mixins for this kind of thing, but some people prefer mixins templates.

+2
source

Source: https://habr.com/ru/post/1274542/


All Articles