【问题标题】:Avoid writing constructor every union style class in C++17避免在 C++17 中为每个联合样式类编写构造函数
【发布时间】:2021-07-03 11:57:24
【问题描述】:

我在一个项目中坚持使用 c++17,所以我无权访问指定的初始化程序。我有一堆联合类型,我想避免以这种方式初始化(​​因为它很烦人):

MyUnionType x;
x.value = value_set_for_all_union_members;

我想要这个

MyUnionType x(value_set_for_all_union_members); 

但我也想避免为我创建的每个联合编写实现。我知道我所有的联合类型都将具有以下结构,每个联合都意味着实际上代表位字段,所以我实际上确实想要在这里进行类型修剪,我知道根据 C++ 它是“UB”,但是在 C++ 委员会中,在 C 中它不是未定义的行为,因此我关心的所有编译器都会在这里做我想做的事情。

union Example{
    integer_type value;
    custom_safe_bitfield_abstraction<...> a;
    custom_safe_bitfield_abstraction<...> b;
    ...
};

我想,好吧,我将只继承构造函数,并使用 CRTP 提取适当的integer_type。当然我不能直接继承联合,所以我选择了这个策略:

struct Example : Base<Example>{
    union{
        integer_type value;
        custom_safe_bitfield_abstraction<...> a;
        custom_safe_bitfield_abstraction<...> b;
        ...
    };
};

使用匿名联合,我应该能够像以前一样使用它(example.value 应该是联合内部的值)。

然后在实现中我执行以下操作:

template<class Derived_T>
struct Base{
    using value_type = decltype(Derived_T::value); 
    explicit Base(value_type v){
        static_cast<Derived_T*>(this)->value = v; 
    }
}

但这不起作用:

error: Incomplete type 'Example' used in nested name specifier
>    using value_type = decltype(Derived_T::value); 

显然,我们不允许在声明成员之前引用它。好的...但是必须有某种方法可以将类型数据提取出来,毕竟我不关心任何内存对齐或任何事情。

我唯一能想到的另一件事是在 CRTP 模板参数中包含类型(即Base&lt;Derived_T, value_type&gt;),但我想避免这样做。我想有一些方法可以在每个派生类上编写函数或指定内部类型,我也不想这样做(而且有点违背了我正在做的事情的目的)。

有没有办法避免为每个类编写构造函数,并且不牺牲我的其他代码重复最小化目标?

【问题讨论】:

  • 我认为你可以使用模板函数而不是继承。
  • 工厂函数
  • @GaryNLOL 你能详细说明一下吗?我想我仍然有“需要从模板中查找成员变量的类型”的问题。
  • FWIW,在 C++ 中通过联合进行类型双关是非法的。我还没有看到编译器不符合大多数人的期望,但你在 UB 领域。根据您实际在做什么/想要什么,std::variant 是您可以使用的类型安全联合类型。
  • @NathanOliver 我想要类型修剪,因为我正在通过它们实现安全位域,我不想存储互斥类型

标签: c++ templates c++17 unions crtp


【解决方案1】:

不完全按照您的要求...但您可以使用这样一个事实,即您可以在成员函数中使用 D::value 的类型...因此在模板构造函数上使用 SFINAE...

我的意思是,你可以写成这样

template <typename D>
struct Base
 {
   template <typename T>
   static constexpr bool is_value_type ()
    { return std::is_same_v<decltype(D::value), T>; }

   template <typename T, bool B = is_value_type<T>(),
             std::enable_if_t<B, int> = 0>
   explicit Base (T v)
    { static_cast<D*>(this)->value = v; }
 };

只有当参数的推导类型与B::value相同时,模板构造函数才启用。

记得添加using

using Base<Example>::Base;

在Example.

以下是完整的编译示例

#include <type_traits>

template <typename D>
struct Base
 {
   template <typename T>
   static constexpr bool is_value_type ()
    { return std::is_same_v<decltype(D::value), T>; }

   template <typename T, bool B = is_value_type<T>(),
             std::enable_if_t<B, int> = 0>
   explicit Base (T v)
    { static_cast<D*>(this)->value = v; }
 };

struct Example : Base<Example>
 {
   using Base<Example>::Base;

   union
    {
      long value;
      long a;
      long b;
    };
 };

int main ()
 {
   //Example  e0{0};   // compilation error
   Example  e1{1l};    // compile
   //Example  e2{2ll}; // compilation error
 }

【讨论】:

  • 我认为这正是我正在寻找的,至少对于我的用例,还请注意,使用Base::Base; 也可以代替Base&lt;T&gt;::Base;。我感到困惑的是为什么这有效。为什么这会成功,在哪里还使用子值的 decltype,而直接尝试获取该类型失败?是因为类本身的定义不再依赖于具体的类型还是什么?
  • @hythis - 类中有些地方您不完全了解(“不完整类型”)模板参数是如何制作的(例如:如果有 value 成员或该成员的类型)。所以decltype(D::value) 在方法的 body 内部工作,但您不能使用它来构造方法本身的签名。
  • 我相信有一点未定义的行为,因为对象是从最基础到最派生的顺序构造的,所以在Base 中访问value构造函数正在访问尚未构造的对象的成员变量。也许在实践中不会有任何问题,但要注意一些事情。
猜你喜欢
  • 2018-03-14
  • 2021-11-10
  • 1970-01-01
  • 2014-03-31
  • 1970-01-01
  • 1970-01-01
  • 2023-02-20
  • 1970-01-01
  • 2016-08-18
相关资源
最近更新 更多