Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to automatically generate operator+ for a class whose data members all implement operator+?

Tags:

c++

templates

Given a plain-old-data C++ class or struct composed of types that implement operator+:

struct VertexData
{
    Vec4 vertex;
    Vec2 texCoord;
};

Is it possible to use templates or some other trick to get the C++ compiler to automatically generate operator+ that adds each member, like this?

VertexData operator+(VertexData const &a, VertexData const &b)
{
     VertexData sum;
     sum.vertex = a.vertex + b.vertex;
     sum.texCoord = a.texCoord + b.texCoord;
     return sum;
}
like image 619
japreiss Avatar asked Sep 09 '13 03:09

japreiss


1 Answers

Disclaimer this is more or less "automatic".

I hope you like templates ;) (Also this requires C++11, most notably because it uses tuples and variadic templates.)

Also, I'd like to thank Rapptz for solving my recursion/iteration over tuple with the indices trick.

So, this is a mix of two ideas I had. The first is the one I posted in the comments earlier. The problem with that idea is that you're restricted to putting all your member variables in a tuple which is very inconvenient. I had another idea which didn't pan out because it involved circular dependency with the Adder template.

So the final idea is you inherit from the adder template which assumes you have a static member variable (of type tuple) in which you put pointers to the member variables you want to add. And the default implementation you inherit creates a new variable of type T, iterates over the parameter the tuple and does member wise addition on them.

You can see it in action here. Do note, you should be able to add support for tostring (or operator <<) the same way as operator+ (instead of manually as I did) and the same for construction from initializer-list.

#include <iostream>
#include <string>
#include <type_traits>
#include <tuple>

template<size_t... Ns>
struct indices {};

template<size_t N, size_t... Ns>
struct build_indices : build_indices<N-1, N-1, Ns...> {};

template<size_t... Ns>
struct build_indices<0, Ns...> : indices<Ns...> {};

template<typename T, typename Tuple, size_t... Indices>
void memberWiseSum(T& lhs, T& rhs, T& sum, const Tuple& typeInfo, indices<Indices...>)
{
    using expander = int[];
    (void)expander{((sum.*std::get<Indices>(typeInfo) = lhs.*std::get<Indices>(typeInfo) + rhs.*std::get<Indices>(typeInfo)), 0)...};
}

template<typename T>
struct Adder
{
    T operator+(Adder<T>& rhs)
    {
        T sum;
        memberWiseSum(*static_cast<T*>(this), *static_cast<T*>(&rhs), *static_cast<T*>(&sum), T::typeInfo, build_indices<std::tuple_size<decltype(T::typeInfo)>::value>{});

        return sum;
    }
};

struct Vec4: public Adder<Vec4>
{
    float x,y,z,w;

    std::string toString()
    {
        return "{" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ", " + std::to_string(w) + "}";
    }

    const static std::tuple<decltype(&Vec4::x), decltype(&Vec4::y), decltype(&Vec4::z), decltype(&Vec4::w)> typeInfo;
};

decltype(Vec4::typeInfo) Vec4::typeInfo(&Vec4::x, &Vec4::y, &Vec4::z, &Vec4::w);

struct Vec2: public Adder<Vec2>
{
    float x,y;

    std::string toString()
    {
        return "{" + std::to_string(x) + ", " + std::to_string(y) + "}";
    }

    const static std::tuple<decltype(&Vec2::x), decltype(&Vec2::y)> typeInfo;
};

decltype(Vec2::typeInfo) Vec2::typeInfo(&Vec2::x, &Vec2::y);

struct VertexData: public Adder<VertexData>
{
    Vec4 vertex;
    Vec2 texCoord;

    std::string toString()
    {
        return "{" + vertex.toString() + ", " + texCoord.toString() + "}";
    }

    const static std::tuple<decltype(&VertexData::vertex), decltype(&VertexData::texCoord)> typeInfo;
};

decltype(VertexData::typeInfo) VertexData::typeInfo(&VertexData::vertex, &VertexData::texCoord);

int main()
{
    VertexData vd1; vd1.vertex.x = 1; vd1.vertex.y = 2; vd1.vertex.z = 3; vd1.vertex.w = 4;
    vd1.texCoord.x = 5; vd1.texCoord.y = 6;
    VertexData vd2; vd2.vertex.x = 1; vd2.vertex.y = 2; vd2.vertex.z = 3; vd2.vertex.w = 4;
    vd2.texCoord.x = 5; vd2.texCoord.y = 6;
    VertexData vd3 = vd1 + vd2;
    std::cout << vd3.toString() << std::endl;

    return 0;
}

Finally, as mentioned in the comments and by Yakk a truly automatic solution would require a reflection system (much like what C# has) but that is for the moment not present in C++.

like image 76
Borgleader Avatar answered Sep 23 '22 11:09

Borgleader