Thread overview
Is there a way to get a template’s parameters and constraints?
Jan 20, 2023
Quirin Schroll
Jan 20, 2023
Adam D Ruppe
Jan 20, 2023
Salih Dincer
Jan 22, 2023
Adam Ross Walker
Jan 22, 2023
Adam Ross Walker
January 20, 2023

Is there a trait (or a combination of traits) that gives me the constraints of a template?

Example:

void f(T1 : long, T2 : const(char)[])(T x) { }
template constraintsOf(alias templ) { /*Magic here*/ }
alias constraints = constraintsOf!f; // tuple(long, const(char)[])

At the moment, I care about constraints that are types. I don’t care about value or alias constraints (e.g. opBinary(string op : "+")(..) or f(alias x : something)(), but if it works for types, it should probably work for other constraints as well.

For what I want, constraintsOf may expect every template parameter to be a type and to have a constraint.

January 20, 2023

On 1/20/23 12:15 PM, Quirin Schroll wrote:

>

Is there a trait (or a combination of traits) that gives me the constraints of a template?

Example:

void f(T1 : long, T2 : const(char)[])(T x) { }
template constraintsOf(alias templ) { /*Magic here*/ }
alias constraints = constraintsOf!f; // tuple(long, const(char)[])

At the moment, I care about constraints that are types. I don’t care about value or alias constraints (e.g. opBinary(string op : "+")(..) or f(alias x : something)(), but if it works for types, it should probably work for other constraints as well.

For what I want, constraintsOf may expect every template parameter to be a type and to have a constraint.

No, there is no way to introspect anything about a template's details until its instantiated.

-Steve

January 20, 2023
On Friday, 20 January 2023 at 17:15:31 UTC, Quirin Schroll wrote:
> Is there a trait (or a combination of traits) that gives me the constraints of a template?

No, reflection over templates is very limited.

January 20, 2023
On Friday, 20 January 2023 at 17:15:31 UTC, Quirin Schroll wrote:
> For what I want, `constraintsOf` may expect every template parameter to be a type and to have a constraint.


If I'm not mistaken, the following will help:

https://dlang.org/phobos/std_range_primitives.html

SDB@79=
January 22, 2023

There is a way but it's horrible. You can take the .stringof and parse the result. I knocked this up for something but it's not well tested and there are probably templates that it handles incorrectly. I'm not claiming this is any good, I just happened to have it.

enum TemplateParameterType { valueType, aliasType, typeType, thisType, sequenceType }

public struct TemplateParameter
{
    TemplateParameterType type;
    string name;
    string defaultValue;
}

public auto templateParameters(alias T)()
{
    static assert(__traits(isTemplate, T));

    import std.algorithm : startsWith;
    import std.array : appender;
    import std.string : strip;

    auto results = appender!(TemplateParameter[]);
    auto isMissing = false;
    const templateSignature = T.stringof;
    const allParametersStart = findExpectedSource(templateSignature,                          '(', isMissing) + 1;
    const allParametersEnd   = allParametersStart +
                              findExpectedSource(templateSignature[allParametersStart .. $], ')', isMissing);

    if (isMissing)
        throw new Exception("{Expected `(` and `)` in template signature: `" ~ templateSignature ~ "`.");

    auto parametersText = templateSignature[allParametersStart .. allParametersEnd];
    auto parameterIndex = -1;

    mainParameterLoop:
    while (parametersText.length > 0)
    {
        parameterIndex++;

        auto parameterEnd = findExpectedSource(parametersText, ',', isMissing);
        if (isMissing)
            parameterEnd = parametersText.length;

        auto parameterRemainingText = parametersText[0 .. parameterEnd];

        if (parameterEnd < parametersText.length)
            parametersText = parametersText[parameterEnd + 1 .. $];
        else
            parametersText = "";

        auto token = parameterRemainingText.consumeToken;

        if (parameterRemainingText.startsWith("..."))
        {
            results.put(TemplateParameter(TemplateParameterType.sequenceType, token, ""));
            continue mainParameterLoop;
        }

        auto nextToken = parameterRemainingText.consumeToken;
        if (nextToken == "")
        {
            results.put(TemplateParameter(TemplateParameterType.typeType, token, ""));
            continue mainParameterLoop;
        }

        TemplateParameterType type;

        if (token == "this")
            type = TemplateParameterType.thisType;
        else if (token == "alias")
            type = TemplateParameterType.aliasType;
        else if (nextToken == ":" || nextToken == "=")
            type = TemplateParameterType.typeType;
        else
            type = TemplateParameterType.valueType;

        if (type != TemplateParameterType.typeType)
        {
            token = nextToken;
            nextToken = parameterRemainingText.consumeToken;
        }

        while (true)
        {
            if (nextToken == "")
            {
                results.put(TemplateParameter(type, token, ""));
                continue mainParameterLoop;
            }

            if (nextToken == ":")
            {
                const identifierName = token;
                while (true)
                {
                    token = parameterRemainingText.consumeToken;
                    if (token.length == 0)
                    {
                        results.put(TemplateParameter(type, identifierName, ""));
                        continue mainParameterLoop;
                    }
                    else if (token == "=")
                    {
                        results.put(TemplateParameter(type, identifierName, parameterRemainingText.strip));
                        continue mainParameterLoop;
                    }
                }

                results.put(TemplateParameter(type, identifierName, parameterRemainingText.strip));
                continue mainParameterLoop;
            }

            if (nextToken == "=")
            {
                const identifierName = token;
                results.put(TemplateParameter(type, identifierName, parameterRemainingText.strip));
                continue mainParameterLoop;
            }

            token = nextToken;

            import std.conv : to;
            if (token.length == 0)
                throw new Exception("Cannot parse parameter " ~ parameterIndex.to!string ~ " in template `" ~ templateSignature ~ "`.");

            nextToken = parameterRemainingText.consumeToken;
        }
    }

    return results[];
}

private auto findExpectedCharacter(string source, char character, out bool isMissing)
{
    foreach (offset; 0 .. source.length)
        if (source[offset] == character)
            return offset;

    isMissing = true;
    return 0;
}

private auto findExpectedText(string source, string text, out bool isMissing)
{
    import std.algorithm : startsWith;

    foreach (offset; 0 .. source.length)
        if (source[offset .. $].startsWith(text))
            return offset;

    isMissing = true;
    return 0;
}

private auto findExpectedSource(string source, char character, out bool isMissing)
{
    auto offset = 0L;
    while (true)
    {
        if (offset >= source.length)
            isMissing = true;
        else if (source[offset] == character)
            return offset;
        else if (source[offset] == '(')
            offset += findExpectedSource(source[offset + 1 .. $], ')', isMissing) + 1;
        else if (source[offset] == '[')
            offset += findExpectedSource(source[offset + 1 .. $], ']', isMissing) + 1;
        else if (source[offset] == '{')
            offset += findExpectedSource(source[offset + 1 .. $], '}', isMissing) + 1;
        else if (source[offset] == '"')
            offset += findExpectedCharacter(source[offset + 1 .. $], '"', isMissing) + 1;
        else if (source[offset] == '\'')
            offset += findExpectedCharacter(source[offset + 1 .. $], '\'', isMissing) + 1;
        else if (source[offset] == '/' && offset + 1 < source.length && source[offset + 1] == '/')
            offset += findExpectedCharacter(source[offset + 1 .. $], '\n', isMissing) + 1;
        else if (source[offset] == '/' && offset + 1 < source.length && source[offset + 1] == '*')
            offset += findExpectedText(source[offset + 1 .. $], "*/", isMissing) + 1;

        if (isMissing)
            return 0;

        offset++;
    }
}

private string consumeToken(ref string text)
{
    import std.uni : isWhite, isAlphaNum;

    // Chew up any preceding white space.
    while (text.length > 0 && text[0].isWhite)
        text = text[1 .. $];

    auto offset = 0;
    while (offset < text.length && (text[offset].isAlphaNum || text[offset] == '_'))
        offset++;

    if (offset == 0 && text.length > 0)
        offset++;

    const result = text[0 .. offset];

    if (offset < text.length)
        text = text[offset .. $];
    else
        text = "";

    return result;
}

unittest
{
    void testTemplate(alias t, size_t line = __LINE__)(TemplateParameter[] expected)
    {
        import std.conv : to;

        const parameters = templateParameters!t;
        assert(parameters == expected,
            "\n\nTest failure on line " ~ line.to!string ~ ": \n" ~
                "Parameters: " ~ parameters.to!string ~ "\n" ~
                "Expected:   " ~ expected.to!string   ~ "\n");
    }

    template t1() { }
    testTemplate!t1([]);

    template t2(int p) { }
    testTemplate!t2([TemplateParameter(TemplateParameterType.valueType, "p", "")]);

    template t3(int p = 1) { }
    testTemplate!t3([TemplateParameter(TemplateParameterType.valueType, "p", "1")]);

    template t4(int p1, string p2) { }
    testTemplate!t4([TemplateParameter(TemplateParameterType.valueType, "p1", ""),    TemplateParameter(TemplateParameterType.valueType, "p2", "")]);

    template t5(int p1 = 123, string p2 = "ABC") { }
    testTemplate!t5([TemplateParameter(TemplateParameterType.valueType, "p1", "123"), TemplateParameter(TemplateParameterType.valueType, "p2", "\"ABC\"")]);

    template t6(alias p) { }
    testTemplate!t6([TemplateParameter(TemplateParameterType.aliasType, "p", "")]);

    template t7(alias p = 12.34) { }
    testTemplate!t7([TemplateParameter(TemplateParameterType.aliasType, "p", "12.34")]);

    class ThisTemplateTest
    {
        template t8(this p) { }
    }

    const thisTemplateTest = new ThisTemplateTest;

    testTemplate!(thisTemplateTest.t8)([TemplateParameter(TemplateParameterType.thisType, "p", "")]);

    template t9(p...) { }
    testTemplate!t9([TemplateParameter(TemplateParameterType.sequenceType, "p", "")]);

    template t10(T) { }
    testTemplate!t10([TemplateParameter(TemplateParameterType.typeType, "T", "")]);

    template t11(alias a_b_c = "a_b_c") { }
    testTemplate!t11([TemplateParameter(TemplateParameterType.aliasType, "a_b_c", "\"a_b_c\"")]);

    class C { }
    template t12(T : C) { }
    testTemplate!t12([TemplateParameter(TemplateParameterType.typeType, "T", "")]);

}
January 22, 2023

On Friday, 20 January 2023 at 17:15:31 UTC, Quirin Schroll wrote:

>

Is there a trait (or a combination of traits) that gives me the constraints of a template?

Apologies, I missed the key part of your question. But I think the above can be adapted if you were so inclined.