可以为数据成员都实现operator +的类自动生成operator +吗?

时间:2013-09-09 03:10:31

标签: c++ templates

给出一个由实现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;
}

4 个答案:

答案 0 :(得分:9)

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

我希望你喜欢模板;)(这也需要C ++ 11,最值得注意的是因为它使用了元组和可变参数模板。)

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

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

所以最后的想法是你从adder模板继承,它假定你有一个静态成员变量(类型为tuple),你可以在其中添加指向你想要添加的成员变量的指针。你继承的默认实现创建一个T类型的新变量,迭代参数元组并对它们进行成员明智的添加。

您可以在行动here中看到它。 请注意,您应该能够以与operator +相同的方式添加对tostring(或operator<<)的支持(而不是像我一样手动添加),并且对于来自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;
}

最后,正如评论和Yakk所提到的,一个真正自动化的解决方案需要一个反射系统(很像C#所具有的),但目前在C ++中并不存在。

答案 1 :(得分:3)

不,这样做需要(至少)编译时反射,能够编写知道类的成员变量列表的代码。

C ++中没有任何这样的构造。有许多建议将编译时反射添加到C ++中,它们可能会出现在C ++ 17中。

如果您更改了类型以使数据不是存储在成员变量中,而是存储在可以反映编译时间的结构中,那么就可以完成。

例如,std::tuple是有序数据的有序元组。存储在std::tuple中的数据可以反映出来,并且可以通过一些工作运算符来执行您所要求的操作。如果不是将数据存储在成员变量中,而是通过继承或组合将其存储在tuple中,您的问题就可以解决了。

您也可以编写自己的反射精简版:如果您编写了一个返回std::tie( each member variable in turn )的成员函数,那么您将对结构内容进行有限的反思。编写一个operator+来运行任何提供这种成员函数的类都不会很难。

现在,这涉及编写几乎与编写实际operator+一样多的代码,但作为奖励,相同的tie函数可用于其他操作(无论是<还是{ {1}}或==-)。我建议使用或CRTP标记您想要自动生成哪些操作,然后使用SFINAE运算符来执行实际工作,如果您采用此路线。 traits类也可以工作(或者,使用CRTP和traits类,默认traits类使用CRTP,但你可以实现自己的)。我对traits类解决方案唯一担心的是运算符上缺少ADL:我不得不手动拉出它们吗?

最后一点,将*实现为成员函数通常被认为是个好主意,然后编写一个使用operator+=实现的免费二进制文件operator+

答案 2 :(得分:1)

正如其他人所说,你不能完全自动完成这个,因为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);
}

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

答案 3 :(得分:0)

不,这是不可能的。

C ++模板非常弱,例如绝对不可能做任何包括枚举类的所有成员的事情。

多年来,一些非常聪明的人试图绕过C ++模板系统的限制,例如他们能够添加模拟数据结构(带有类型列表),模拟循环(带递归),模拟条件(使用SFINAE)。 / p>

事实上,C ++模板元编程是“模板”元编程,它并没有考虑到编译时算法,而只是作为一个比C宏略好的基于模板的替换。即使是C ++ 11也基本上没有增加任何内容。

对于一些奇怪的马戏效果(“嘿妈妈,看看我能做些什么”)这种类型的战斗变得有点流行,即使现在你也可以找到显然聪明的家伙仍在努力解决元编程问题而无需使用数小时适当的工具,只是为了表明他们可以。

这当然只能回复半复杂的错误解决方案,这些解决方案也会给编译器带来压力,例如,当你使用那些脆弱的结构犯错时,通常可以获得无意义的bab呀声而不是明确的错误信息。 / p>

确实,那些复杂的机器试图解决的问题在很大程度上是非出口的,只是通过选择差的元编程语言来强加。 如果您喜欢那种编程可能会更好地朝着那个方向前进并让C ++使用记事本直接在brainfuck中编写应用程序。

如果您确实需要解决元编程问题,那么可能需要使用外部C ++生成器(或者您可以从C ++转向支持元编程的语言)。