Search code examples
ooptemplatesd

Specifying a function with templates that takes and returns an arbitrary class


I'm interested in defining a function that given a class variable, generates and a new instance of the class object with a randomly selected member attribute mutated.

Context: Consider an instance, circle1, of some class, Circle, has attributes color and radius. These attributes are assigned values of red and 5, respectively. The function in question, mutate, must accept circle1 as an argument, but reject non-class arguments.

For other data types, templates provide an answer in this context. That is, templates may be used to specify generic instances of functions that can accept arguments of multiple types.

How can a generic function that accepts (and returns) an instance of any class be defined using templates?


Solution

  • In general, if you need to restrict what a template can take, you use template constraints. e.g.

    import std.traits : isIntegral;
    
    auto foo(T)(T t)
        if(isIntegeral!T)
    {
        ...
    }
    

    or

    import std.functional : binaryFun;
    
    auto foo(alias pred, T, U)(T t, U u)
        if(is(typeof(binaryFun!pred(t, u.bar())) == bool)
    {
        ...
    }
    

    As long the condition can be checked at compile time, you can test pretty much anything. And it can be used for function overloading as well (e.g. std.algorithm.searching.find has quite a few overloads all of which are differentiated by template constraint). The built-in __traits, the eponymous templates in std.traits, and is expressions provide quite a few tools for testing stuff at compile time and then using that information in template constraints or static if conditions.

    If you specifically want to test whether something is a class, then use an is expression with == class. e.g.

    auto foo(T)(T t)
        if(is(T == class))
    {
        ...
    }
    

    In general though, you'll probably want to use more specific conditions such as __traits(compiles, MyType result = t.foo(22)) or is(typeof(t.foo(22)) == MyType). So, you could have something like

    auto mutate(T)(T t)
        if(is(T == class) &&
           __traits(compiles, t.color = red) &&
           __traits(compiles, t.radius = 5))
    {
        ...
    }
    

    If the condition is something that you want to reuse, then it can make sense to create an eponymous template - which is what's done in Phobos in places like std.range.primitives and std.range.traits. For instance, to test for an input range, std.range.primitives.isInputRange looks something like

    template isInputRange(R)
    {
        enum bool isInputRange = is(typeof(
        {
            R r = R.init;     // can define a range object
            if (r.empty) {}   // can test for empty
            r.popFront();     // can invoke popFront()
            auto h = r.front; // can get the front of the range
        }));
    }
    

    Then code that requires an input range can use that. So, lots of functions in Phobos have stuff like

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

    A more concrete example would be this overload of find:

    InputRange find(alias pred = "a == b", InputRange, Element)
                   (InputRange haystack, Element needle)
        if(isInputRange!InputRange &&
           is(typeof(binaryFun!pred(haystack.front, needle)) : bool))
    {
        ...
    }
    

    Ali Çehreli's book, Programming in D, has several relevant chapters, including:

    http://ddili.org/ders/d.en/templates.html
    http://ddili.org/ders/d.en/cond_comp.html
    http://ddili.org/ders/d.en/is_expr.html
    http://ddili.org/ders/d.en/templates_more.html