Book Image

D Cookbook

By : Adam Ruppe
Book Image

D Cookbook

By: Adam Ruppe

Overview of this book

Table of Contents (21 chapters)
D Cookbook
Credits
Foreword
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Creating a user-defined vector type


User-defined types are used everywhere in D to group data, model objects, provide compile-time checks, and more. Here, you'll create a simple vector type with a length and direction to look at some basic capabilities.

Getting ready

Whenever you create a user-defined collection in D, the first decision to make is whether it should be a class, struct, mixin template, or union. Mixin templates are great for code reuse. They define code that can be copied (or mixed in) to another type, with parameterization. Unions are for the cases when you need the same block of memory to have multiple types, and are the least common in typical D code. Classes and structs are the backbone of user-defined types in D, and they have the most in common. The key difference is polymorphic inheritance; if you need it, you probably want a class. Otherwise, structs are lighter weight and give maximum flexibility. Using them, you can precisely define the layout of each byte with no hidden data, overload all operators, use deterministic destruction (the RAII idiom from C++), and use both reference or value semantics depending on your specific needs. D's structs also support a form of subtyping, though not virtual functions, which you'll see in Chapter 6, Wrapped Types.

Let's summarize as follows:

Struct

Class

This offers precise control over memory layout

This offers virtual functions and inheritance

This is ideal for lightweight wrappers of other types

This is always a reference type

This offers deterministic destruction

This is usually managed by the garbage collector

Since your vector type will not need virtual functions, it will be a struct.

How to do it…

Let's look at creating a vector type using the following steps:

  1. Declare the struct variable with a name. This declaration can appear anywhere; but, in your case, you want it to be generally accessible. So, it should go in the top-level scope of your module. Unlike C++, there is no need to put a semicolon at the end of the struct definition, as shown in the following code:

    struct Vector {}
  2. Determine which data members are needed and add them to the struct. Here, you need a magnitude and direction, and they will be floating point types:

    struct Vector {
        float magnitude;
        float direction;
    }
  3. Add methods that operate on the data to the struct. In this case, you want to be able to add vectors together and convert from (x, y) coordinates. The complete code is as follows:

    struct Vector {
        // the data
        float magnitude;
        float direction;
    
        // the methods
        /// create a Vector from an (x, y) point
        static Vector fromPoint(float[2] point) {
             import std.math;
             Vector v;
             float x = point[0];
             float y= point[1];
             v.magnitude = sqrt(x ^^ 2 + y ^^ 2);
             v.direction = atan2(y, x);
             return v;
        }}}
        /// converts to an (x,y) point. returns in an array.
        float[2] toPoint() const {
             import std.math;
             float x = cos(direction) * magnitude;
             float y = sin(direction) * magnitude;
             return [x, y];
        }
        /// the addition operator
        Vector opBinary(string op : "+")(Vector rhs) const {
             auto point = toPoint(), point2 = rhs.toPoint();
             point[0] += point2[0];
             point[1] += point2[1];];];
    
             return Vector.fromPoint(point););
        }
    }
  4. Use the new type as follows:

    auto origin = Vector(0, 0);
    import std.math;
    auto result = origin + Vector(1.0, PI);
    import std.stdio;
    writeln("Vector result: ", result);
    writeln(" Point result: ", result.toPoint());

It will print Vector(1.0, 3.14) and [-1, 0], showing the vector sum as magnitude and direction, and then x, y. Your run may have slightly different results printed due to differences in how your computer rounds off the floating point result.

How it works…

Structs are aggregate types that can contain data members and function methods. All members and methods are defined directly inside the struct, between the opening and closing braces. Data members have the same syntax as a variable declaration: a type (which can be inferred, if there is an initializer), a name, and optionally, an initializer. Initializers must be evaluated at compile time. When you declare a struct, without an explicit initializer, all members are set to the value of their initializers inside the struct definition.

Methods have the same syntax as functions at module scope, with two differences; they can be declared static and they may have const, immutable, or inout attached, which applies to the variable this. The this variable is an automatically declared variable that represents the current object instance in a method. The following recipe on immutability will discuss these keywords in more detail.

Operator overloading in D is done with methods and special names. In this section, you defined opBinary, which lets you overload the binary operators such as the addition and subtraction operators. It is specialized only on the + operator. It is also possible to overload casting, assignment, equality checking, and more.

At the usage point, you declared a vector with auto, using the automatically defined constructor.

Finally, when you write the result, you use the automatic string formatting that prints the name and the values, in the same way as the automatic constructor. It is also possible to take control of this by implementing your own toString method.

See also

  • Chapter 6, Wrapped Types, will show more advanced capabilities, including how to use structs to make a reference type and to use constructors, destructors, postblits, and so on.

  • Inheritance and dynamic class casting will show how to make the most of classes.

  • Visit http://dlang.org/operatoroverloading.html for the language documentation on operator overloading. It details all the operators available for overloading and how to do it.