如何使这个“template / constexpr”构造更优雅/更简洁?

时间:2015-03-16 23:38:22

标签: c++ templates static auto constexpr

我有这个伪位域实现:

class Field {
public:
  constexpr Field(int i, int s) : index(i), size(s) {}
  constexpr Field(const Field & prev, int s) : index(prev.index + prev.size), size(s) {}
  int index, size;
};

#define FIELD(name, i, s) constexpr static const Field name = {i, s};

template<typename T = quint32>
class Flags {
public:
  Flags(T d = 0) : data(d) {}
  inline T readField(const Field & f) {
    return (data & getMask(f.index, f.size)) >> f.index;
  }
  inline void writeField(const Field & f, T val) {
    data = (data & setMask(f.index, f.size)) | (val << f.index);
  }
private:
  static constexpr T getMask(int i, int size) {
    return ((1 << size) - 1) << i;
  }
  static constexpr T setMask(int pos, int size) {
    return ~getMask(pos, size);
  }
  T data;
};

但是,以目前的形式使用它是非常冗长的:

struct Test {
  Flags<> flags;
  FIELD(one, 0, 1)
  FIELD(two, one, 2)
};

Test t;
t.flags.readField(t.one);
t.flags.writeField(t.one, 1);

我想让它更优雅,所以我可以简单地执行此操作而不是上面的语法:

t.one.read();
t.one.write(1);

我尝试这样做的方法是为每个Flags &设置Field,并实施read()write()方法,这些方法在内部使用Flags目标

然而,这要求Field也成为模板,这进一步增加了详细程度,现在还必须为字段指定T

我尝试使用T隐式指定Flags<T>::makeField()但很快就变得混淆了constexprtstatic与常规成员和方法之间的不兼容性{{1进入圈子后最终决定寻求有经验的人的建议。

当然,要求auto不占用运行时存储,并且在编译期间解析尽可能多的表达式。

6 个答案:

答案 0 :(得分:2)

完全不知道你的意图是什么,我的第一个建议就是简单地使用一个位域。它简单/快速等等一千倍。

struct Test {
     unsigned long long one : 1;
     unsigned long long one : 2;
};

但是,如果你真的想要一个类,我创建了一个FieldReference类,看起来模糊地匹配你正在做的事情。

类别:

#include <cassert>
#include <type_traits>
#include <cstddef>

template<class T, size_t offset_, size_t size_>
struct FieldReference {
    static const size_t offset = offset_;
    static const size_t size = size_;
    static const size_t mask = ~T(((~0)<<offset<<size)|((1<<offset)-1));

    explicit FieldReference(T& f) :flags(&f) {}
    operator T() const {return (flags[0]&mask)>>offset;}
    FieldReference& operator=(T v) {
        assert((v&~(mask>>offset))==0);
        flags[0] &= ~mask;
        flags[0] |= (v<<offset);
        return *this;
    }
private:
    T* flags;
};
#define FIRSTFIELD(Flags,Name,Size) \
    auto Name() -> FieldReference<decltype(Flags),0,Size> {return FieldReference<decltype(Flags),0,Size>(Flags);} \
    FieldReference<std::add_const<decltype(Flags)>::type,0,Size> Name() const {return FieldReference<std::add_const<decltype(Flags)>::type,0,Size>(Flags);}
#define FIELD(Flags,Name,Previous,Size) \
    auto Name() -> FieldReference<decltype(Flags),decltype(Previous())::offset,Size> {return FieldReference<decltype(Flags),decltype(Previous())::offset,Size>(Flags);} \
    auto Name() const -> FieldReference<std::add_const<decltype(Flags)>::type,decltype(this->Previous())::offset,Size> {return FieldReference<std::add_const<decltype(Flags)>::type,decltype(Previous())::offset,Size>(Flags);}

用法:

struct Test {
    unsigned long long flags = 0;
    FIRSTFIELD(flags,one,1);
    FIELD(flags,two,one,2);
};

#include <iostream>

int main() {
    Test t;
    t.one() = 1; //That seems less verbose
    t.two() = 3;
    std::cout << t.two();
    return 0;
}

http://coliru.stacked-crooked.com/a/c027d9829ce05119

这些字段不会占用任何空间,除非你正在处理它们,即使这样它们也只占用一个指针的空间。所有偏移量,大小和掩码都是在编译时计算的,所以这应该比你的代码更快。

答案 1 :(得分:2)

这是如何真正接近你想要的。请记住,你说实施可能很难看。 :)

template<class T, T mask, T bitpos>
class Field {
  T &d_t;
public:
  Field(T &t) : d_t(t) {}
  T read() const {
    return (d_t & mask) >> bitpos;
  }
  void write(T const &t)  {
    d_t = (d_t & ~mask) | (t << bitpos);
  }
};


#define BTFDENUMDECL1(name, width) name##start
#define BTFDENUMDECL2(name, width, ...) name##start, name##end =  name##start + width - 1, BTFDENUMDECL1(__VA_ARGS__)
#define BTFDENUMDECL3(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL2(__VA_ARGS__)
#define BTFDENUMDECL4(name, width, ...) name##start, name##end = name##start + width - 1, BTFDENUMDECL3( __VA_ARGS__)

#define BTFNMEMBER1(field, name, width) auto name() {           \
    return Field<decltype(field), ((1 << width) - 1) << name##start, name##start>(field); }

#define BTFNMEMBER2(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER1(field, __VA_ARGS__)
#define BTFNMEMBER3(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER2(field, __VA_ARGS__)
#define BTFNMEMBER4(field, name, width, ...) BTFNMEMBER1(field, name, width) BTFNMEMBER3(field, __VA_ARGS__)

#define GET_MACRO(_1,_1_,_2,_2_,_3,_3_,_4,_4_, NAME, ...) NAME

#define BITFIELDS(field, ...)                       \
  private: uint32_t field;                      \
  enum E##flags { GET_MACRO(__VA_ARGS__, BTFDENUMDECL4,0, BTFDENUMDECL3,0, BTFDENUMDECL2,0, BTFDENUMDECL1,0)(__VA_ARGS__) }; \
public:                                 \
 GET_MACRO(__VA_ARGS__, BTFNMEMBER4,0, BTFNMEMBER3,0, BTFNMEMBER2,0, BTFNMEMBER1,0)(field, __VA_ARGS__)

这适用于C ++ 14,但可以减少&#34;减少&#34;到C ++ 11。它最多支持4个字段,但您可以通过简单克隆BTFDENUMDECLBTFNMEMBER宏并更新GET_MACRO来添加更多字段。您可以使用它:

struct Test {
  BITFIELDS(flags,
    one, 1,
    two, 2,
    three, 3
    );
};

并在代码中:

Test test;
test.one().write(0);
std::cout << test.one().read() << std::endl;

因此,只有括号添加到您想要的语法中。

快速解释:我们(ob)使用宏的可变数量的参数。我们一次采用两个参数(来自变量参数&#34; list&#34;) - 您发现的大多数示例一次只能获取一个参数,因此这有点不寻常。在BITFIELDS宏中,我们首先使用字段的位位置定义枚举,然后为每个字段定义一个函数。字段函数返回一个具有read()write()方法的代理,它使用枚举中定义的位位置。

这是一个简单的实现。您可以添加自己的铃声和口哨声,例如检查write()中的范围),另一个宏来为字段提供类型(如BITFIELDST(T, field, ...))等。

对于C ++ 98,你必须稍微重构一下(删除__VA_ARGS__的用法),并通过给出宏名称中的参数数量来使用它&#34; call&# 34;,如BITFIELDS4

答案 2 :(得分:1)

看起来没有其他人在咬人,所以我只想提到想到的两种方法。

我认为这里的关键是一个能够修改特定值而不占用任何存储空间的字段。因此,实现这一目标的语言特征将是:

匿名联盟,它提供了 test.one.read()的语法。

清空基础优化,它会为 test.one()。read()提供一种语法。

前者没有实际位逻辑的快速示例 - 此示例中的所有字段只修改整个值。按位逻辑将是微不足道的:

template< typename T >
struct Bitmask
{
    T m_val;
};

template< typename BITMASK, int INDEX >
struct Field : private BITMASK
{
    int read() const { return BITMASK::m_val; }
    void write( int i ) { BITMASK::m_val = i; }
};

struct Test
{
    typedef Bitmask<int> Flags;
    union
    {
        Flags m_flags;
        Field<Flags,0> one;
        Field<Flags,1> two;
        Field<Flags,2> three;
    };
};

这符合您的具体用法,但需要注意的是Field也是模板化的。就像旁注一样,我真的认为无论事情是如何完成的,它应该是 test.m_flags.one.read()或类似的,因为如果这些位是唯一但通常命名的,那么这允许任何类使用Flags实例可以使其中的多个没有问题。

空基础优化功能我还没有被模拟,但该功能将返回一个访问者对象 - 很像你的例子中的字段,但是需要的&#39;标志&amp;&#39;参数已被绑定。

空基也可能需要单继承和一些转换。从好的方面来说,我认为可以准确地支持位掩码中的位数。因此,如果需要3位,它可能存储为 unsigned char ,但只存在函数one(),two()和three()。​​

如果您愿意,如果我有时间,我也可以模拟这个例子。

据我所知,这些技术应该有效,所以我有兴趣知道它们是否不便携,如果是,那么出于什么原因。< / p>

编辑:快速阅读cppreference关于工会的部分,表明标准不支持从工会的非活跃成员那里读取数据。然而,主要的编译器支持它。所以这个方法存在一个问题。

答案 3 :(得分:1)

首先,如果你想达到这样的效果:

int main() {
    Test t;
    cout << t.one.read() << endl;
    t.one.write(1);
    cout << t.one.read() << endl;
}

您必须通知onetwo他们将操纵flags - 所以第一次更改 - 将flags添加为FIELD的参数:< / p>

struct Test {
  Flags<> flags;
  FIELD(one,          0, 1, flags);
//                          ^^^^^                                      
  FIELD(two, AFTER(one), 2, flags);
//           ^^^^^^^^^^
};

其他更改是我修改了将前一个标记作为index应用于下一个标记的方式 - 请参阅使用AFTER宏。我相信它现在更具可读性,它简化了我的提议。 AFTER之后会出现,首先让我们讨论更重要的魔法。

所以,我引入了FieldManip类来操作给定的标志:

template <typename Flags, int i, int s>
class FieldManip {
public:
    constexpr static const Field field = {i, s};

    FieldManip(Flags* flags) : flags(flags) {}
    auto read()
    {
        return flags->readField(field);
    }
    template <typename T>
    void write(T value)
    {
        flags->writeField(field, value);
    }
private:
    Flags* flags;
};

它需要额外的内存大小:sizeof(Flags*),但优秀的编译器应优化任何CPU开销。我担心没有别的方法可以支付额外的内存,除非你想放宽你的要求 - 所以onetwo可以是成员函数,而不是成员变量......

因此,新FLAGS的定义很简单:

#define FIELD(name, i, s, flags) \
  FieldManip<decltype(flags), i, s> name{&flags}

是时候解释AFTER

首先,对Field进行了一些简化,删除了额外的构造函数并添加了after独立函数:

class Field {
public:
  constexpr Field(int i, int s) : index(i), size(s) {}
  int index, size;
};
constexpr int after(const Field& prev) { return prev.index + prev.size; }

但是在宏观中我们FieldManip而不是Field - 所以需要下一个功能:

template <typename FieldManip>
constexpr int after() { return after(FieldManip::field); }

AFTER

#define AFTER(fieldmanip) after<decltype(fieldmanip)>()

答案 4 :(得分:1)

为什么要调用方法?

首先,略有不同的旗帜:

template<class C, size_t N=0, class D=uint32_t>
struct Flags {
  Flags(Flags const&)=default;
  Flags():data() {} // remember to zero!
  Flags(D raw):data(raw) {}

  template<unsigned start, unsigned width>
  constexpr void set( D bits ) {
    D m = mask<start, width>();
    data &= ~m;
    data |= (bits<<start)&m;
  }
  template<unsigned start, unsigned width>
  constexpr D get( D bits ) const {
    D m = mask<start, width>();
    return (data&m)>>start;
  }
private:
  template<unsigned start, unsigned width>
  static constexpr D mask() {
    return ((1<<(width)-1)<<start;
  }
  D data;
};

我们的Flags现在按其所在容器的类型输入。

如果您想在容器中放置多个Flags,请传递n的索引。

namespace details {
  template<class T> struct tag{using type=T;};

  template<class C, size_t n, unsigned start, unsigned width>
  struct field {};

  template<class Flags, class Field>
  struct pseudo_ref {
    Flags& flags;
    template<class U>
    constexpr pseudo_ref operator=( U&& u )const{
      field_assign( flags, Field{}, std::forward<U>(u) );
      return *this;
    }
    template<class T>
    constexpr operator T()const{
      return field_get( tag<T>{}, flags, Field{} );
    }
  };

  template<class C, size_t n, class D, unsigned start, unsigned width>
  constexpr auto operator*(
    Flags<C,n,D>& flags, field<C, n, start, width>
  )
  -> psuedo_ref<Flags<C,n,D>, field<C,n,start,width>>
  {
    return {flags};
  }
  template<class C, size_t n, class D, unsigned start, unsigned width>
  constexpr auto operator*(
    Flags<C,n,D> const& flags, field<C,n,start,width>
  )
  -> psuedo_ref<Flags<C,n,D> const, field<C,n,start,width>>
  {
    return {flags};
  }
  template<class C, size_t n, class D, unsigned start, unsigned width, class U>
  void field_assign( Flags<C,n,D>& flags, field<C,n,start,width>, U&& u ){
    flags.set<start, width>(std::forward<U>(u));
  }
  template<class T,class C, size_t n, class D, unsigned start, unsigned width>
  constexpr T field_get(
    tag<T>, Flags<C,n,D> const& flags, field<C,n,start,width>
  ) {
    return flags.get<start,width>();
  }
  template<class F>
  struct field_end;
  template<class C, size_t n, unsigned start, unsigned width>
  struct field_end:std::integral_constant<unsigned, start+width>{};
}
template<class C, unsigned width, size_t n=0>
using first_field = field<C,n,0,width>;
template<class C, class F, unsigned width, size_t n=0>
using next_field = field<C,n,details::field_end<F>::value,width>;

现在语法如下:

struct Test {
  Flags<Test> flags;
};
first_field<Test, 1> one;
next_field<Test, decltype(one), 2> two;

Test t;
uint32_t o = t*one;
t*one = 1;

并且所有内容都会自动发送到比特代码。

注意完全没有宏。您可以使用一个删除上面的decltype

答案 5 :(得分:0)

我知道过于复杂且令人难以置信的C ++ 11和C ++ 14功能是时髦的,但这是使用邪恶宏的快速而肮脏的解决方案:

#define GETMASK(index, size) (((1 << size) - 1) << index)
#define READFROM(data, index, size) ((data & GETMASK(index, size)) >> index)
#define WRITETO(data, index, size, value) (data = (data & (~GETMASK(index, size))) | (value << index))
#define FIELD(data, name, index, size) \
  inline decltype(data) name() { return READFROM(data, index, size); } \
  inline void set_##name(decltype(data) value) { WRITETO(data, index, size, value); }

然后简单地说:

struct Test {
  uint flags;
  FIELD(flags, one, 0, 1)
  FIELD(flags, two, 1, 2)
};

t.set_two(3);
cout << t.two();

它为字段生成访问器,就好像它们是包含对象的属性一样,它更简洁,IMO也非常易读,因为这是人们从封装数据中公开对象属性的常见方式。

下行 - 您必须自己计算字段索引,但您仍然可以使用该方法与现有实现一起生成访问器,依赖于带有构造函数的静态字段来避免它。

好处 - 它简短,简单,高效且向后兼容 - 将decltype更改为typeof,它将适用于pre-c ++ 11甚至是纯C。