【问题标题】:How to combine slicing, templates and non derived classes?如何结合切片、模板和非派生类?
【发布时间】:2018-12-27 12:15:31
【问题描述】:

我想要一个可以摄取对象的容器,并将它的不同部分存储到不同的内部容器中。

类似这样的:

// Pseudo-code

template<typename A, typename B, typename C>
class Container {
   std::vector<B> vb;
   std::vector<C> vc;
public:
   void push_back(const A &a) {
      vb.push_back(a);
      vc.push_back(a);
   }
};

class AA {
   int a;
   std::string s;
};

class BB {
   int a;
};

class CC {
   std::string s;
};

Container<AA, BB, CC> cont;

AA aa;
cont.push_back(aa);

我希望对象 vb 获得 A 类和 B 类(切片)之间的共同点,对象 vc 也是如此。

理想情况下,AA、BB 和 CC 类不应处于层级关系中。我希望编译器能够按成员的类型匹配成员。

我更喜欢性能损失较小的解决方案。

【问题讨论】:

  • 对象切片是类层次结构的一个特性。如果没有反射,尝试在没有继承的情况下实现类似的东西会非常棘手。
  • “编译器能够按成员的类型匹配成员”,这几乎是要求编译器读懂你的想法?您必须以一种或另一种方式指定您想要的东西。
  • @PasserBy:这取决于。如果没有同类型的成员变量,就不用读我的心思了。如果有,如果编译器可以匹配具有相同类型和相同名称的成员,那就太好了。

标签: c++ templates derived-class object-slicing


【解决方案1】:

您可以使用标签调度对类型进行分类并有条​​件地选择它们的成员。一个例子:

template<class T> typename std::is_same<decltype(T::s), std::string>::type test_has_string_s(int);
template<class T> std::false_type test_has_string_s(...);
template<class T> using HasStringS = decltype(test_has_string_s<T>(0));

template<class T> typename std::is_same<decltype(T::a), int>::type test_has_int_a(int);
template<class T> std::false_type test_has_int_a(...);
template<class T> using HasIntA = decltype(test_has_int_a<T>(0));

template<typename B, typename C>
class Container {
    std::vector<B> vb;
    std::vector<C> vc;

    template<class T> void push_back_vb(T const& t, std::true_type) { vb.push_back(B{t.s}); }
    template<class T> void push_back_vb(T const&, std::false_type) {}

    template<class T> void push_back_vc(T const& t, std::true_type) { vc.push_back(C{t.a}); }
    template<class T> void push_back_vc(T const&, std::false_type) {}

public:
    template<class T>
    void push_back(T const& t) {
        static_assert(HasStringS<T>::value || HasIntA<T>::value, "Must have any of {T::a, T::s}.");
        push_back_vb(t, HasStringS<T>{});
        push_back_vc(t, HasIntA<T>{});
    }
};

struct AA {
   int a;
   std::string s;
};

struct BB {
   std::string s;
};

struct CC {
   int a;
};

struct DD {};

int main() {
    Container<BB, CC> cont;
    cont.push_back(AA{});
    cont.push_back(BB{});
    cont.push_back(CC{});
    cont.push_back(DD{}); // error: static assertion failed: Must have any of {T::a, T::s}.
}

【讨论】:

  • 与 user2079303 相比,您的方法有什么优势吗?
  • @Pietro 这个解决方案是非侵入式的:不需要修改你传递给Container::push_back的类。
  • 好点。由于它的简单性,我会选择其他解决方案,但我感谢您的更好的界面。
【解决方案2】:

您的伪代码容器已经在AA 继承BBCC 的情况下工作,因为切片允许从派生到基的隐式转换。但是,您的实施并不限于这种情况。所需要的只是AA 可以隐式转换为BBCC。继承就足够了,但不是必需的。

您可以通过定义转换运算符使一个类可转换为其他类:

struct AA {
   operator BB() const { return {a}; }
   operator CC() const { return {s}; }
   int a;
   std::string s;
};

或者,如果您希望依赖项采用另一种方式,您可以将转换构造函数添加到 BB 和/或 CC

struct BB {
   BB(const AA& aa) : a(aa.a) {}
   int a;
};

但请注意,缩小诸如此类的隐式转换可能会导致某些错误被忽视。

【讨论】:

  • 你的意思是:BB operator() const { return {a}; } 吗?但如果是这种情况,编译器就无法根据返回类型选择正确的运算符。
  • @Pietro 不,我不是那个意思,因为那是不正确的。转换运算符是根据 push_backargument 类型选择的。
  • 对不起,我没明白。 operator BB() const 是伪代码,还是正确的 C++?运算符不应该有返回类型吗? BB() 是一个有效的运算符吗?
  • @Pietro 这都是正确的 C++。这是转换运算符的语法。 BB 是返回类型。
猜你喜欢
  • 2021-06-10
  • 2019-11-09
  • 1970-01-01
  • 2020-01-20
  • 2016-08-17
  • 2016-02-17
  • 2015-04-10
  • 2018-10-16
  • 1970-01-01
相关资源
最近更新 更多