首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >是否可以为其数据成员都实现operator+的类自动生成operator+?

是否可以为其数据成员都实现operator+的类自动生成operator+?
EN

Stack Overflow用户
提问于 2013-09-09 11:10:31
回答 4查看 394关注 0票数 8

给定一个由实现operator+的类型组成的普通旧数据C++类或结构

代码语言:javascript
复制
struct VertexData
{
    Vec4 vertex;
    Vec2 texCoord;
};

有没有可能使用模板或其他技巧让C++编译器自动生成添加每个成员的operator+,如下所示?

代码语言:javascript
复制
VertexData operator+(VertexData const &a, VertexData const &b)
{
     VertexData sum;
     sum.vertex = a.vertex + b.vertex;
     sum.texCoord = a.texCoord + b.texCoord;
     return sum;
}
EN

回答 4

Stack Overflow用户

发布于 2013-09-09 13:14:38

免责声明这或多或少是“自动”的。

我希望您喜欢模板;) (这也需要C++11,因为它使用元组和可变模板。)

另外,我要感谢Rapptz用索引技巧解决了我在元组上的递归/迭代。

所以,这是我有两个想法的混合。第一个是我之前在评论中发布的。这种想法的问题是,您只能将所有成员变量放在一个元组中,这非常不方便。我有另一个想法,但没有成功,因为它涉及到Adder模板的循环依赖。

因此,最后的想法是继承adder模板,该模板假设您有一个静态成员变量(类型为tuple),在该变量中放置了指向要添加的成员变量的指针。继承的默认实现创建一个T类型的新变量,迭代参数元组,并对它们进行成员相加。

您可以在操作here中看到它。请注意,您应该能够以与operator+相同的方式添加对tostring (或运算符<<)的支持(而不是像我那样手动添加),并对从初始化器列表进行构造的支持相同。

代码语言:javascript
复制
#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++中还没有这个系统。

票数 9
EN

Stack Overflow用户

发布于 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+是一个好主意。

票数 3
EN

Stack Overflow用户

发布于 2013-09-10 14:18:07

正如其他人所说,您不能完全自动完成此操作,因为C++没有反射。但是,如果您愿意实现一个访问所有成员指针的成员函数,则可以获得很多功能。下面的示例不仅会生成operator+,还会生成operator+=operator==operator<

代码语言:javascript
复制
#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);
}

这可以很容易地扩展到大多数需要在所有成员上工作的东西。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/18690979

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档