D doesn't have specialized syntax for user-defined literals, but using the template syntax with the right implementation, we can generate the same code as if it did. A distinguishing factor of user-defined literals is that there must be zero runtime cost to use them. Here, we'll write the code to enable integer literals written in octal, a simplified version of the implementation of octal
in Phobos' std.conv
module.
First, let's write an octal number parser, converting from string to integer. The algorithm is simple: read a digit, multiply our accumulator by eight, and then add the value to the accumulator until we're out of digits. Any invalid digit is an exception. All these steps are shown in the following code:
int readOctalString(string n) { int sum = 0; foreach(c; n) { if(c < '0' || c > '7') throw new Exception("Bad octal number " ~ n); sum *= 8; sum += c - '0'; } return sum; } unittest { assert(readOctalString("10") == 8); assert(readOctalString("15") == 13); assert(readOctalString("4") == 4); import std.exception; assertThrown!Exception(readOctalString("90")); }
Let's execute the following steps to create user-defined literals:
enum myname = yourFunction(s);
.The code is as follows:
// step 2: template octal(string s) { enum octal = readOctalString(s); } // step 3: octals also make sense with some int literals template octal(int i) { import std.conv; enum octal = octal!(to!string(i)); } // usage test: void main() { import std.stdio; writeln(octal!10); writeln(octal!"15"); writeln(octal!4); }
It prints 8
, 13
, and 4
, matching our preceding unit test. Let's also confirm the compiler outputs the same code as it will for a normal integer literal by looking at the following disassembly:
$ objdump -d -M intel simpleoctal | grep _Dmain -A 30 0808f818 <_Dmain>: 808f818: 55 push ebp 808f819: 8b ec mov ebp,esp 808f81b: b8 08 00 00 00 mov eax,0x8 808f820: e8 e3 7d 00 00 call 8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv> 808f825: b8 0d 00 00 00 mov eax,0xd 808f82a: e8 d9 7d 00 00 call 8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv> 808f82f: b8 04 00 00 00 mov eax,0x4 808f834: e8 cf 7d 00 00 call 8097608 <_D3std5stdio14__T7writelnTiZ7writelnFiZv> 808f839: 31 c0 xor eax,eax 808f83b: 5d pop ebp 808f83c: c3 ret
The relevant lines are mov eax, 0x8
, mov eax, 0xd
, and mov eax, 0x4
. They are indistinguishable from any other integer literals, and there are no calls to octal
or readOctalString
in sight! (The calls after those lines are to writeln
.)
This is an example of the general pattern we'll be exploring in this chapter. We will write a regular function to convert input from one form to another, and then use D to evaluate that function at compile time to generate code or data.
The enum
keyword is central to our result. In D, the enum
keyword is used for all named constants that do not have a memory address. This, of course, includes traditional enumerations such as enum Color { red, green, blue }
, and it also works as a single value. In both cases, the value works just like a literal value at the usage site, as if you copied and pasted the value straight to the usage point.
When you declare an enum
structure with a value, the right-hand side of the equation is immediately evaluated at compile time, regardless of complexity, and it will cause a compile-time error if this is unsuccessful for any reason. This is an extension of constant folding as seen in almost all the compiled languages, where if you write 1 + 2
, the compiler automatically translates that to 3 instead of a literal adding an instruction in the compiled program. D simply takes that concept much farther.
Therefore, the enum
keyword is the D idiom to force a compile-time function evaluation to create a literal in the generated code.
The enum
keyword is not the only way to trigger compile-time evaluation, and it is not always the best way. It is also run for a static
initializer, such as a static
variable or an initializer in a class or a struct
definition, or anywhere else where the regular code can not be immediately run. Since the enum
array literals always allocate at the usage point, they are often better represented as a static immutable
variable.
The next task is to ensure we get the enum
keyword's behavior without being forced to do the following tedious two-step process:
enum ourOctalPermissionLiteral = readOctalString("755"); chmod("my_file", ourOctalPermissionLiteral);
Since the compile-time evaluation and literal behavior only happens in specific contexts like enum
, trying to call the readOctalString
function directly will produce a plain runtime function call. We need to use the enum
keyword as an intermediary. That's where the short octal
template comes into play.
A template is fundamentally a block of code with parameters which act as placeholders that are replaced at the usage point to generate new code. We've used them extensively throughout this book to create new types and functions. Here, we're using a template to create an enum
value from a string placeholder.
When the octal
template is used, the enum
structure is created on the spot and substituted for the template, giving us the convenience of template usage syntax with the behavior of enum
literals, achieving our goal.
enum
were both designed features, but the usefulness of the combination was only realized later.13.58.51.228