给定一个由实现operator+的类型组成的普通旧数据C++类或结构
struct VertexData
{
Vec4 vertex;
Vec2 texCoord;
};有没有可能使用模板或其他技巧让C++编译器自动生成添加每个成员的operator+,如下所示?
VertexData operator+(VertexData const &a, VertexData const &b)
{
VertexData sum;
sum.vertex = a.vertex + b.vertex;
sum.texCoord = a.texCoord + b.texCoord;
return sum;
}发布于 2013-09-09 13:14:38
免责声明这或多或少是“自动”的。
我希望您喜欢模板;) (这也需要C++11,因为它使用元组和可变模板。)
另外,我要感谢Rapptz用索引技巧解决了我在元组上的递归/迭代。
所以,这是我有两个想法的混合。第一个是我之前在评论中发布的。这种想法的问题是,您只能将所有成员变量放在一个元组中,这非常不方便。我有另一个想法,但没有成功,因为它涉及到Adder模板的循环依赖。
因此,最后的想法是继承adder模板,该模板假设您有一个静态成员变量(类型为tuple),在该变量中放置了指向要添加的成员变量的指针。继承的默认实现创建一个T类型的新变量,迭代参数元组,并对它们进行成员相加。
您可以在操作here中看到它。请注意,您应该能够以与operator+相同的方式添加对tostring (或运算符<<)的支持(而不是像我那样手动添加),并对从初始化器列表进行构造的支持相同。
#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;
}最后,正如评论和Yakk提到的那样,一个真正自动化的解决方案需要一个反射系统(很像C#所拥有的系统),但目前C++中还没有这个系统。
发布于 2013-09-09 11:47:26
不,这样做需要(至少)编译时反射,即编写能够识别类的成员变量列表的代码的能力。
在C++中没有这样的构造。有许多关于将编译时反射添加到C++中的建议,它们可能会出现在C++17中。
如果您更改了类型,以便将数据存储在可在编译时反映的结构中,而不是存储在成员变量中,则可以做到这一点。
例如,std::tuple是类型化数据的有序元组。存储在std::tuple中的数据可以在上面反映,并且只需做一点工作,就可以编写出可以工作的运算符。如果不是将数据存储在成员变量中,而是通过继承或组合将其存储在tuple中,那么问题就可以解决了。
您还可以编写自己的反射精简版:如果您编写了一个返回std::tie( each member variable in turn )的成员函数,那么它将对您的结构的内容进行有限的反射。编写一个可以在任何提供这种成员函数的类上操作的operator+并不难。
现在,这涉及到编写几乎与编写实际operator+一样多的代码,但额外的好处是,相同的tie函数可以用于其他操作(可以是<、==、-或*)。我建议使用或CRTP来标记你想要自动生成的操作,然后使用SFINAE运算符来做实际工作,如果你这样做的话。特征类也可以工作(或者,使用CRTP和特征类,其中默认的特征类使用CRTP,但您可以实现自己的)。对于特征类解决方案,我唯一担心的是运算符缺乏ADL :我怀疑你必须手动引入它们?
最后,通常认为将operator+=实现为成员函数,然后编写使用+=实现的自由二进制operator+是一个好主意。
发布于 2013-09-10 14:18:07
正如其他人所说,您不能完全自动完成此操作,因为C++没有反射。但是,如果您愿意实现一个访问所有成员指针的成员函数,则可以获得很多功能。下面的示例不仅会生成operator+,还会生成operator+=、operator==和operator<:
#include <assert.h>
// Base class for classes that have a visitMembers method.
template <typename Derived>
struct MemberVisitable {
operator Derived&() { return static_cast<Derived&>(*this); }
operator const Derived&() const { return static_cast<const Derived&>(*this); }
protected:
MemberVisitable() { } // Force this class to be used only as a base class.
};
// Define operator< for MemberVisitables
////////////////////////////////////////
template <typename T>
struct CompareLess {
const T &a;
const T &b;
bool &result;
template <typename U>
bool operator()(U T::*member) const
{
const U& x = a.*member;
const U& y = b.*member;
if (x<y) {
result = true;
return false;
}
if (y<x) {
result = false;
return false;
}
return true;
}
};
template <typename Derived>
inline bool
operator<(
const MemberVisitable<Derived> &a1,
const MemberVisitable<Derived> &a2
)
{
bool result = false;
CompareLess<Derived> visitor = {a1,a2,result};
Derived::visitMembers(visitor);
return result;
}
// Addition
///////////
template <typename T>
struct AddTo {
T &a;
const T &b;
template <typename U>
bool operator()(U T::*member) const
{
(a.*member) += (b.*member);
return true;
}
};
template <typename Derived>
inline Derived
operator+(
const MemberVisitable<Derived> &a1,
const MemberVisitable<Derived> &a2
)
{
Derived result = a1;
AddTo<Derived> visitor = {result,a2};
Derived::visitMembers(visitor);
return result;
}
template <typename Derived>
Derived&
operator+=(
MemberVisitable<Derived> &a1,
const MemberVisitable<Derived> &a2
)
{
AddTo<Derived> visitor = {a1,a2};
Derived::visitMembers(visitor);
return a1;
}
// Equality
/////////////
template <typename T>
struct CompareEqual {
const T &a;
const T &b;
template <typename U>
bool operator()(U T::*member) const
{
return (a.*member) == (b.*member);
}
};
template <typename Derived>
bool
operator==(
const MemberVisitable<Derived> &a1,
const MemberVisitable<Derived> &a2
)
{
CompareEqual<Derived> visitor = {a1,a2};
return Derived::visitMembers(visitor);
}
// Test with our own struct
/////////////////////////////
struct A : MemberVisitable<A> {
float b;
int c;
A(float b,int c) : b(b), c(c) { }
template <typename Visitor>
static bool visitMembers(const Visitor &visit)
{
return visit(&A::b) && visit(&A::c);
}
};
int main(int,char**)
{
A a1(1,2);
A a2(3,5);
assert(a1<a2);
assert(!(a2<a1));
assert(!(a1<a1));
A a3 = a1+a2;
assert(a3.b==4);
assert(a3.c==7);
a1 += a2;
assert(a1==a3);
}这可以很容易地扩展到大多数需要在所有成员上工作的东西。
https://stackoverflow.com/questions/18690979
复制相似问题