D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 12732 - Add an Appender-like template that recursively builds a structure of Appender fields
Summary: Add an Appender-like template that recursively builds a structure of Appender...
Status: RESOLVED WONTFIX
Alias: None
Product: D
Classification: Unclassified
Component: phobos (show other issues)
Version: D2
Hardware: All All
: P1 enhancement
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2014-05-11 14:33 UTC by Andrej Mitrovic
Modified: 2022-07-06 05:17 UTC (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Andrej Mitrovic 2014-05-11 14:33:31 UTC
This seems to be a common occurrence in my code:

-----
import std.array;

alias vec3 = int[3];

///
struct Model
{
    vec3[] indices;
    vec3[] vertices;
    vec3[] normals;
}

Model loadModel(string path)
{
    // note the code duplication here, needs to be tracked 
    // separately to the Model struct.
    Appender!(vec3[]) indices;
    Appender!(vec3[]) vertices;
    Appender!(vec3[]) normals;

    // ...
    // indices ~= 
    // vertices ~= 

    return Model(indices.data, vertices.data, normals.data);
}

void main() { }
-----

To avoid this code duplication it would be great to have a helper template that can return an equivalent structure which contains Appender fields for all internal arrays, and a convenience .data property function that returns the original type. Here's one implementation:

-----
import std.array;
import std.typetuple;

alias vec3 = int[3];

alias ApplyAppender(T : E[], E) = Appender!T;
alias ApplyAppender(T) = T;

struct AppenderWrapper(T)
{
    alias Fields = staticMap!(ApplyAppender, typeof(T.tupleof));
    Fields fields;
    alias fields this;

    @property T data()
    {
        T res;

        foreach (idx, field; fields)
        {
            static if (is(typeof(res.tupleof[idx]) : E[], E))
                res.tupleof[idx] = field.data;
            else
                res.tupleof[idx] = field;
        }

        return res;
    }
}

///
struct Model
{
    vec3[] indices;
    vec3[] vertices;
    vec3[] normals;
}

Model loadModel(string path)
{
    AppenderWrapper!(typeof(return)) result;

    // ...
    // result.indices ~=
    // result.vertices ~=

    return result.data;
}

void main() { }
-----
Comment 1 Andrej Mitrovic 2014-05-11 14:39:21 UTC
Oops, I failed to properly test that implementation. Lemme fix that up real soon.
Comment 2 Andrej Mitrovic 2014-05-11 14:45:44 UTC
Here's a working version, unfortunately I again had to resort to string mixins:

-----
import std.array;
import std.string;
import std.typetuple;

alias vec3 = int[3];

template ApplyAppender(alias S)
{
    static if (is(typeof(S) : E[], E))
        enum ApplyAppender = format("Appender!(%s) %s;", typeof(S).stringof, __traits(identifier, S));
    else
        enum ApplyAppender = format("%s %s;", typeof(S).stringof, __traits(identifier, S));
}

string generate(T)()
{
    string[] res;

    foreach (str; staticMap!(ApplyAppender, T.tupleof))
        res ~= str;

    return res.join("\n");
}

struct AppenderWrapper(T)
{
    mixin(generate!T);

    @property T data()
    {
        T res;

        foreach (idx, field; this.tupleof)
        {
            static if (is(typeof(res.tupleof[idx]) : E[], E))
                res.tupleof[idx] = field.data;
            else
                res.tupleof[idx] = field;
        }

        return res;
    }
}

///
struct Model
{
    vec3[] indices;
    vec3[] vertices;
    vec3[] normals;
    int other;
}

Model loadModel(string path)
{
    AppenderWrapper!(typeof(return)) result;

    vec3 vec;
    result.indices ~= vec;
    result.vertices ~= vec;
    result.normals ~= vec;
    result.other = 5;

    return result.data;
}

void main() { }
-----
Comment 3 Andrej Mitrovic 2014-05-11 14:47:33 UTC
And because of string mixins the mixin() won't work if the type is from another module which isn't imported. Well maybe we can implement this without string mixins somehow.
Comment 4 Andrej Mitrovic 2014-05-11 14:52:18 UTC
There we go:

-----
import std.array;
import std.string;
import std.typetuple;

alias ApplyAppender(T : E[], E) = Appender!T;
alias ApplyAppender(T) = T;

enum Identifier(alias S) = __traits(identifier, S);

string generateAliases(T)()
{
    string[] res;

    foreach (idx, str; staticMap!(Identifier, T.tupleof))
        res ~= format("alias %s = fields[%s];", str, idx);

    return res.join("\n");
}

struct AppenderWrapper(T)
{
    alias Fields = staticMap!(ApplyAppender, typeof(T.tupleof));
    Fields fields;

    mixin(generateAliases!T);

    @property T data()
    {
        T res;

        foreach (idx, field; fields)
        {
            static if (is(typeof(res.tupleof[idx]) : E[], E))
                res.tupleof[idx] = field.data;
            else
                res.tupleof[idx] = field;
        }

        return res;
    }
}

alias vec3 = int[3];

struct Model
{
    vec3[] indices;
    vec3[] vertices;
    vec3[] normals;
    int other;
}

Model loadModel(string path)
{
    AppenderWrapper!(typeof(return)) result;

    vec3 vec;
    result.indices ~= vec;
    result.vertices ~= vec;
    result.normals ~= vec;
    result.other = 1;

    return result.data;
}

void main() { }
-----
Comment 5 Andrej Mitrovic 2022-07-06 05:17:59 UTC
This doesn't need to be part of Phobos, it's a very specific use-case that can easily be implemented in a user library (or even just a few lines of code these days).