【问题标题】:multiple optional members in class template without overhead类模板中的多个可选成员,无需开销
【发布时间】:2021-11-12 06:30:02
【问题描述】:

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

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 个可选类成员,我必须编写 2^3=8 个类,即“小重复”很快变得不合理。

一个可能的解决方案可能是像这样使用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';
  }
};

但是现在,我班级的对象会浪费内存。我正在寻找某种方法来避免大部分代码重复(在可选成员代码开销的数量上最多呈线性),同时避免花费不必要的运行时间和内存。

请注意,对于单个可选类成员(请参阅Optional class members without runtime overheadMost efficient way to implement template-based optional class members in C++?)存在此问题(有答案),但据我所知,对于多个可选成员,该问题尚未得到解答。

【问题讨论】:

  • 也许你不知道,如果用户想查看你的问题的历史,他们可以在这里看到:stackoverflow.com/posts/69222391/revisions。您无需添加“编辑:...”或保留旧内容
  • 可变参数模板是否可接受,或者t1t2t3 是真正不同的含义(即&lt;int, void, int&gt;&lt;int, int, void&gt;)。
  • @463035818_is_not_a_number 我选择写“编辑:...”而不是更改问题,因为现在阅读您的评论的人可能会错误地认为它引用了当前形式的问题。跨度>
  • 他们没有,因为评论被删除了 ;) 考虑到这种情况经常发生(cmets 指的是较旧的修订版)并且 cmets 的寿命相当短,而重要的是问题中的内容
  • @Jarod42 我不确定我是否理解这个问题。是的,&lt;int, void, int&gt; 应该是与 &lt;int, int, void&gt; 不同的类,但是,如果我理解正确的话,这与可变参数模板没有冲突。

标签: c++ templates optional-parameters


【解决方案1】:

使用 optional_member 类,

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&lt;TX, tagX&gt; 而不是上述解决方案中的OptionalMember&lt;TX&gt;

【讨论】:

  • 很有趣,我们可以用size_t tag_num 替换class Tag 吗?这应该使我们能够使用可变参数模板,从而泛化到任意数量的可选成员,对吧?
  • 是的,std::size_t 是备选方案。
【解决方案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;
}

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

【讨论】:

  • 你应该添加标签,因为X&lt;int, int&gt;目前是不可能的。
  • 拥有static const bool has_member; 也可能有用。
【解决方案3】:

可以使用继承只编写 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&lt;T2&gt;)(如上)实现:

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';
  }
};

注意:如果不是所有 Ts 都是 void,这才是最佳选择,因为如果我没记错的话,类在 C++ 中不能有 size-0(我假设因为没有 2 个对象在记忆)。

【讨论】:

  • 您没有两次相同的基础,因此 EBO 可能适用于所有人。即使&lt;void, void, void&gt; 也可以。
  • 对,如果您通过继承使用 optional_tuple,但如果您有 optional_tuple 类型的成员,则不会 - 我不熟悉 [[no_unique_address]] 寿,我必须仔细阅读...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多