A function to add two flat structs

I’d like to be able to add two structs without knowing about the structs ahead of time, such as:

function Base.:+(a::T, b::T) where {T}
    T((getfield(a, field) + getfield(b, field) for field in fieldnames(a))...)
end

I find that for a simple struct (with an int and a float) that this takes about a microsecond, which is much longer than I expected.

I also tried it this way with mutable structs:

function Base.:+(a::T, b::T) where {T}
    s = deepcopy(a)
    for field in fieldnames(a)
        setfield!(s, field, getfield(a, field) + getfield(b, field))
    end
    return s
end

That takes about 2us.

If I hard-code the addition, like so:

addem(x::MyType, y::MyType) = MyType(x.a + y.a, x.b + y.b)

it only takes about 20ns. This is more like what I’m hoping for, but without hard-coding the addition.

Is there a better way to do this generally?

Use a generated function to build the MyType addition statement using fieldnames.

Here’s an example of a function that copies all field of a generic abstract type. It’s similar to what you want and should be easy to modify.

https://github.com/JuliaDiffEq/DiffEqBase.jl/blob/master/src/data_array.jl#L57

1 Like

Wow, thanks @ChrisRackauckas. Here’s my first take at a generated function:

@generated function Base.:+(x::T, y::T) where T
    expr = ( :(x.$f + y.$f) for f in fieldnames(T) ) # Make a tuple of expressions to add each field
    :( T($(expr...)) ) # Splat them into an expression for a constructor for a T type
end

This only takes ~20ns or so, just like the hard-coded version.

3 Likes