8

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

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

4 回答 4

9

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

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

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

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

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

您可以在这里看到它的实际效果。请注意,您应该能够以与 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++ 中不存在。

于 2013-09-09T05:14:37.827 回答
3

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

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

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

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

您还可以编写自己的反射精简版:如果您编写了一个返回的成员函数std::tie( each member variable in turn ),那将使您对结构内容的反射有限。编写一个operator+可以在任何提供此类成员函数的类上运行的类并不难。

现在,这涉及编写几乎与编写实际代码一样多的代码operator+,但作为奖励,相同的tie函数可以用于其他操作(无论是 it <or ==or -or *)。我建议使用或 CRTP 来标记您想要自动生成的操作,然后使用 SFINAE 运算符来完成实际工作,如果您走这条路线的话。特征类也可以工作(或者,使用 CRTP 和特征类,其中默认特征类使用 CRTP,但您可以实现自己的)。我对特征类解决方案唯一担心的是操作员缺少 ADL:我怀疑您必须手动将它们拉入?

最后,将其实现operator+=为成员函数通常被认为是一个好主意,然后编写一个operator+使用+=.

于 2013-09-09T03:47:25.590 回答
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);
}

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

于 2013-09-10T06:18:06.940 回答
0

不,这是不可能的。

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

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

事实仍然是,C++ 模板元编程是“模板”元编程,它在设计时并没有考虑到编译时算法,而只是作为比 C 宏更好的基于模板的替换。甚至 C++11 也基本上没有添加任何东西。

对于一些奇怪的马戏团效应(“嘿,妈妈,看看我能做什么”)这种战斗变得有点流行,即使现在你可以发现显然聪明的人仍然在不使用适当工具的情况下为解决元编程问题而奋斗无数小时,只是为了表明他们可以。

这当然只能回馈半工作的过度复杂的糟糕解决方案,这些解决方案也确实给编译器带来了压力,例如,当您使用这些脆弱的结构犯错时,通常会得到一堆废话而不是清晰的错误消息。

事实上,那些复杂机器试图解决的问题在很大程度上是不存在的,只是通过选择一种糟糕的元编程语言而自己强加的。如果您喜欢这种编程,那么最好一直朝那个方向前进,让 C++ 直接使用记事本在Brainfuck中编写应用程序。

相反,如果您确实需要解决元编程问题,那么可能需要一个外部 C++ 生成器(或者您可以从 C++ 转移到对元编程有严格支持的语言)。

于 2013-09-09T05:49:39.810 回答