class 模板中的多个可选成员,无开销

multiple optional members in class template without overhead

如果我想要一个带有可选成员的 class,我正在使用模板专业化:

template<class T>
struct X {
  T t;
  void print() { cout << "t is " << t << '\n'; }
};
template<>
struct X<void> {
  void print() { cout << "without T\n"; }
};

这很好,因为没有运行时开销并且代码重复很少。 但是,如果我有 3 个而不是 1 个可选 class 成员,我必须写 2^3=8 classes,也就是说,“小重复”很快变得不合理。

一个可能的解决方案是像这样使用 std::conditional

template<class T1, class T2, class T3>
struct X {
  conditional_t<is_void_v<T1>, char, T1> t1;
  conditional_t<is_void_v<T2>, char, T2> t2;
  conditional_t<is_void_v<T3>, char, T3> t3;

  void print() {
    if constexpr (!is_void_v<T1>) cout << "t1 is " << t1 << '\n';
    if constexpr (!is_void_v<T2>) cout << "t2 is " << t2 << '\n';
    if constexpr (!is_void_v<T3>) cout << "t3 is " << t3 << '\n';
  }
};

但是现在,我 class 的对象浪费了内存。我正在寻找一些方法来避免大部分代码重复(减少到最多与可选成员代码开销成线性关系),同时避免花费不必要的运行时间和内存。

请注意,对于单个可选 class 成员(请参阅 , )存在此问题(有答案),但据我所知,尚未针对多个可选成员回答此问题。

可以使用继承只写 2*3=6 类 并将数据与使用它的实际代码分开:

template<class T1> struct optional_tuple1 {T1 first;};
template<> struct optional_tuple1<void> {};

template<class T1, class T2> struct optional_tuple2: public optional_tuple1<T1> { T2 second;};
template<class T1> struct optional_tuple2<T1, void>: public optional_tuple1<T1> {};

template<class T1, class T2, class T3> struct optional_tuple3: public optional_tuple2<T1, T2> {T3 third;};
template<class T1, class T2> struct optional_tuple3<T1, T2, void>: public optional_tuple2<T1, T2> {};

但是,如果 T2void,您必须确保没有使用变量 second。这可以通过 if constexpr (!std::is_void_v<T2>) (如上所述)来实现:

template<class T1, class T2, class T3>
struct X3: public optional_tuple3<T1, T2, T3> {
  void print() {
    if constexpr (!is_void_v<T1>) cout << "first is " << this->first << '\n';
    if constexpr (!is_void_v<T2>) cout << "second is " << this->second << '\n';
    if constexpr (!is_void_v<T3>) cout << "third is " << this->third << '\n';
  }
};

注意:只有在并非所有 T 都是 void 时,这才是最优的,因为如果我没记错的话,类 在 C++ 中不能有 size-0(我假设因为没有 2 个对象可能在内存中接收到相同的地址。

这样的事情怎么样:

template<typename T>
struct member_haver {
    T m_member;
};

template<>
struct member_haver<void> {
};

template<typename... Ts>
struct X : member_haver<Ts>... {
    void print() {
        ((std::cout << member_haver<Ts>::m_member << ", "), ...);
    }
};

然后,例如,你可以说:

int main() {
    X<int, void, float> x{};
    x.member_haver<int>::m_member = 1;
    x.member_haver<float>::m_member = 5.2f;

    x.print();

    return 0;
}

当然这可能需要在语法上进行改进,但要点就在那里。

有 optional_member class,

template <class T>
struct OptionalMember
{
    T t;
    static constexpr bool has_member = true;
};

template<> struct X<void>
{
    static constexpr bool has_member = false;
};

您可以使用继承(只要它们的类型不同)和 EBO 来避免额外的内存。

template<class T1, class T2, class T3>
struct X : OptionalMember<T1>, OptionalMember<T2>, OptionalMember<T3>
{
  void print() {
    if constexpr (!OptionalMember<T1>::has_member)
        cout << "t1 is " << OptionalMember<T1>::t << '\n';
    if constexpr (!OptionalMember<T2>::has_member)
        cout << "t2 is " << OptionalMember<T2>::t << '\n';
    if constexpr (!OptionalMember<T1>::has_member)
        cout << "t3 is " << OptionalMember<T3>::t << '\n';
  }
};

或者,自 C++20 起,属性 [[no_unique_address]](只要它们的类型不同,就没有额外的内存)。

template<class T1, class T2, class T3>
struct X {
  [[no_unique_address]] OptionalMember<T1> t1;
  [[no_unique_address]] OptionalMember<T2> t2;
  [[no_unique_address]] OptionalMember<T3> t3;

  void print() {
    if constexpr (!t1.has_member) cout << "t1 is " << t1.t << '\n';
    if constexpr (!t2.has_member) cout << "t2 is " << t2.t << '\n';
    if constexpr (!t3.has_member) cout << "t3 is " << t3.t << '\n';
  }
};

如果类型可能相同,您可以修改 OptionalMember 以获取额外的标签(或任何类型的标识符作为 std::size_t):

template <class T, class Tag>
struct OptionalMember
{
    T t;
    static constexpr bool has_member = true;
};

template<class Tag> struct X<void, Tag>
{
    static constexpr bool has_member = false;
};

然后

struct tag1;
struct tag2;
struct tag3;

并使用 OptionalMember<TX, tagX> 而不是上述解决方案中的 OptionalMember<TX>