Chapter 4. Running Code at Compile Time

The title of this chapter refers to Compile Time Function Execution (CTFE). This is primarily what makes D's generative programming capability as powerful as it is. Given that a function meets certain constraints, the compiler can execute it in order to produce values at compile time. These can then be used to generate new code. The very basics of CTFE can be explained with a couple of paragraphs, but there are a number of related features that can be used to increase its benefits. These features can also be used independently to conditionally control which parts of the program are compiled, or to generate code without ever running a function. We're going to spend the entire chapter examining these features in preparation for the next chapter on templates; D's compile-time features coupled with templates make for amazing possibilities.

  • Pragmas: Compile-time messages, libraries, and function inlining hints
  • Conditional compilation: version, debug, and static if conditions
  • Compile-time strings: The import expression and string mixins
  • Compile-time function execution
  • Odds and ends: static assert, alignment, compile-time reflection, and user-defined attributes

Pragmas

A pragma statement is a directive for the compiler to perform a specific task at compile time. In C and C++, it's a preprocessor directive, but in D it's an actual statement. At the time of writing, there are five predefined pragmas. We'll go through three of them here; the other two are for more advanced usage. The language also allows for vendor-specific pragmas. When a compiler encounters one that it doesn't recognize, such as one from a different compiler vendor, it is required to emit an error. Vendor-specific pragmas can be used by versioning them, something you'll learn how to do in the next section. For more on pragmas, refer to http://dlang.org/pragma.html.

The lib pragma

The lib pragma is a way to instruct the compiler in code as to which libraries should be linked at compile time. Here's an example.

pragma(lib, "OpenGL32.lib");

This will cause the compiler to insert a directive into the object file that the linker can then use to link a library into the executable, OpenGL32.lib in this case. While it's a useful feature, there are some potential issues to be aware of.

First, the library names are always going to be platform-specific. The preceding example is specific to Windows. Later in this chapter, I'll show you how to version sections of your code to target specific platforms, but bear in mind that you'll generally need at least two lib pragmas for a multi-platform project: one for Windows and one for POSIX.

Second, just as when passing the name of a library on the command line, any libraries passed through the lib pragma will be searched for on the global search path, including any paths specified on the command line. While there is no way to specify a search path via a pragma, it's possible to specify a complete path to a library, like so:

pragma(lib, `C:dlanglibsMyLib.lib`);

Notice the format of the string. When compiling using DMD on Windows with the OPTLINK linker (the default 32-bit configuration), it's necessary to specify paths using backslashes, as OPTLINK doesn't understand forward slashes. To avoid the need to escape the backslashes (''), I've used a WYSIWYG string. When using the MS linker, forward slashes suffice.

Third, you might one day have the idea of adding a lib pragma in one of the source modules of a library you distribute, hoping to make it more convenient for users by having it link to itself. In my experience, this tends to cause more trouble than it's worth. The potential for conflict with the user's preferred build system is high. I've actually seen a couple of libraries distributed with full paths specified in lib pragmas, something that's almost guaranteed to cause a build failure out of the box. It's generally a better idea to let the user, or the user's preferred build tool, decide how to link with your library and any of its dependencies so that conflicts can be avoided. An exception will be if the library depends on a system library that should be generally available on most systems. Adding a lib pragma in one of your modules for that library should be safe if it's properly versioned.

Finally, when linking multiple libraries via lib pragmas, the order in which they are declared matters when linking with the GNU linker. The compiler will queue them up in lexical order and pass them all to the linker, but the GNU linker requires that dependencies be ordered after dependents. For example, if a hypothetical libA3 depends on libA2, and libA2 depends on libA1, then they need to be passed along in the following order: libA3, libA2, and libA1. To ensure DMD does the right thing, the lib pragmas must be ordered the same way.

pragma(lib, "A3");
pragma(lib, "A2");
pragma(lib, "A1");

This also holds true when passing libraries on the command line. When using lib pragmas, it's quite easy to get a working program on Windows that fails to link on Linux or Mac. The lesson here is to always familiarize yourself with the system tools you're working with before using any features that depend on the toolchain.

The lib pragma is most useful when compiling your own executables. The format for specifying global library search paths varies across compilers and systems (we'll take a look at some examples later in the book), so including the full path to a library in the source is one option that may be preferable to specifying it in the build system, except when using libraries that are managed by DUB.

The msg pragma

When generating code at compile time, it's sometimes necessary to output error or debug messages. writeln and friends are not executable at compile time, so they aren't going to help. Enter the msg pragma.

pragma(msg, "Hi! I'm a compile-time message.");

When the compiler encounters this pragma, it will immediately print the message to the console. This usually isn't the desired behavior; it's often more useful to have it printed out only in specific circumstances. Very soon, we'll take a look at other compile time features that make that possible.

The inline pragma

The compiler does not attempt to inline function calls unless -inline is passed on the command line. The inline pragma can be used to affect its behavior.

pragma(inline, false) void dontInlineMe() {...}
pragma(inline, true):
  void pleaseInlineMe() {...}
  int meToo() {...}
pragma(inline): // go back to the default behavior

Here, dontInlineMe will never be inlined, even when the compiler thinks it's a good idea to do so. If the compiler is unable to inline either of the next two functions, it will generate an error. Finally, pragma(inline) restores the default behavior, so any functions declared from that point on in the module will be inlined or not, at the compiler's discretion. To be very clear, none of these have any effect if -inline is not passed on the command line.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.140.204