Search code examples
d

D - static if on variadic arguments not working properly


Suppose I have the following variadic function, which job is to concat together a path from pieces (each piece can either be an index of integral type, or a node of string type):

string 
makePath(P...)(P path)
{
    import std.conv;
    import std.format;

    string res;
    foreach (piece; path) {
        pragma(msg, typeof(piece).stringof);
        static if (is(piece: size_t))
            res = res is null ? piece.to!string : format("%s[%s]", res, piece);
        else if (is(piece: string))
            res = res is null ? piece : format("%s.%s", res, piece);
        else
            static assert(0);
    }
}

If I later use it like this: string path = makePath("foo", "bar"), somehow the code reaches the static assert(0); and the compilation terminates. This is most curious, but the pragma actually writes string as the type of the first argument despite the fact that a code path for some other type was taken.

Even better, using makePath(12, 13) results in the compiler complaining about both the line for strings (about incompatible types of int and string) and static assert. What is going on here?

I've tried this both on DMD and LDC.


Solution

  • The is keyword is what's at fault here. (It's quite a confusing keyword I find...)

    I'd recommend you use the templates in std.traits to test for types, most there are covered: https://dlang.org/phobos/std_traits.html

    Here's a working version of your function:

    string 
    makePath(P...)(P path)
    {
        import std.conv;
        import std.format;
        import std.traits : isSomeString, isNumeric;
    
        string res;
        foreach (piece; path) {
            static if (isNumeric!(typeof(piece)))
                res = res is null ? piece.to!string : format("%s[%s]", res, piece);
            else static if (isSomeString!(typeof(piece))) // Note, you were missing the 'static' on the 'else if' part
                res = res is null ? piece : format("%s.%s", res, piece);
            else
                static assert(0);
        }
        return res;
    }
    
    unittest {
        static assert(makePath("foo","bar") == "foo.bar");
        static assert(makePath("foo","bar",1) == "foo.bar[1]");
    }