是否有推荐的 "safe" 方法来指定由常量标志值组成的位和弦?

Is there a recommended "safe" way to specify a bit-chord made out of constant flag-values?

情况:偶尔我会写一个可以接受多个布尔参数的函数,而不是像这样写:

void MyFunc(bool useFoo, bool useBar, bool useBaz, bool useBlah);

[...]

// hard to tell what this means (requires looking at the .h file)
// not obvious if/when I got the argument-ordering wrong!
MyFunc(true, true, false, true);

我希望能够使用定义的位索引的位和弦来指定它们,如下所示:

enum {
   MYARG_USE_FOO = 0,
   MYARG_USE_BAR,
   MYARG_USE_BAZ,
   MYARG_USE_BLAH,
   NUM_MYARGS
};

void MyFunc(unsigned int myArgsBitChord);

[...]

// easy to see what this means
// "argument" ordering doesn't matter
MyFunc((1<<MYARG_USE_FOO)|(1<<MYARG_USE_BAR)|(1<<MYARG_USE_BLAH));

效果很好,因为它允许我轻松传递大量布尔参数(作为单个 unsigned long,而不是一长串单独的布尔值),而且我可以很容易地看到我的对 MyFunc() 的调用正在指定(无需返回到单独的头文件)。

如果我愿意,它还允许我迭代定义的位,这有时很有用:

unsigned int allDefinedBits = 0;
for (int i=0; i<NUM_MYARGS; i++) allDefinedBits |= (1<<i);

主要缺点是它可能有点容易出错。尤其是这样很容易搞砸,误做:

// This will compile but do the wrong thing at run-time!
void MyFunc(MYARG_USE_FOO | MYARG_USE_BAR | MYARG_USE_BLAH);

...甚至犯下这个经典的拍额头错误:

// This will compile but do the wrong thing at run-time!
void MyFunc((1<<MYARG_USE_FOO) | (1<<MYARG_USE_BAR) || (1<<MYARG_USE_BLAH));

我的问题是,有推荐的 "safer" 方法吗?即我仍然可以轻松地将多个定义的布尔值作为单个参数中的位和弦传递,并且可以迭代定义的位值,但是像上面显示的 "dumb mistakes" 将被编译器捕获而不是而不是在运行时导致意外行为?

#include <iostream>
#include <type_traits>
#include <cstdint>

enum class my_options_t : std::uint32_t {
    foo,
    bar,
    baz,
    end
};

using my_options_value_t = std::underlying_type<my_options_t>::type;

inline constexpr auto operator|(my_options_t const & lhs, my_options_t const & rhs)
{
    return (1 << static_cast<my_options_value_t>(lhs)) | (1 << static_cast<my_options_value_t>(rhs));
}

inline constexpr auto operator|(my_options_value_t const & lhs, my_options_t const & rhs)
{
    return lhs | (1 << static_cast<my_options_value_t>(rhs));
}

inline constexpr auto operator&(my_options_value_t const & lhs, my_options_t const & rhs)
{
    return lhs & (1 << static_cast<my_options_value_t>(rhs));
}

void MyFunc(my_options_value_t options)
{
    if (options & my_options_t::bar)
        std::cout << "yay!\n\n";
}

int main()
{
    MyFunc(my_options_t::foo | my_options_t::bar | my_options_t::baz);
}

您可以使用位域,它允许您高效地构造具有单独命名的位标志的结构。

例如,您可以将 struct myOptions 传递给函数,它定义为:

struct myOptions {
  unsigned char foo:1;
  unsigned char bar:1;
  unsigned char baz:1;
};

然后,当您必须构建要发送给函数的值时,您可以这样做:

myOptions opt;
opt.foo = 1;
opt.bar = 0;
opt.baz = 1;
MyFunct(opt);

位域紧凑且高效,但允许您将位(或位组)命名为独立变量。

顺便说一句,考虑到声明的冗长,这是我可能会打破每个语句只声明一个变量的常见风格的一个地方,并按如下方式声明结构:

struct myOptions {
  unsigned char foo:1, bar:1, baz:1;
};

并且,在 C++20 中,您可以添加初始值设定项:

struct myOptions {
  unsigned char foo:1{0}, bar:1{0}, baz:1{0};
}

模板怎么样...

#include <iostream>

template <typename T>
constexpr unsigned int chordify(const T& v) {
    return (1 << v);
}

template <typename T1, typename... Ts>
constexpr unsigned int chordify(const T1& v1, const Ts&... rest) {
    return (1 << v1) | chordify(rest... );
}

enum {
   MYARG_USE_FOO = 0,
   MYARG_USE_BAR,
   MYARG_USE_BAZ,
   MYARG_USE_BLAH,
   NUM_MYARGS
};

int main() {
    static_assert(__builtin_constant_p(
        chordify(MYARG_USE_FOO, MYARG_USE_BAZ, MYARG_USE_BLAH)
    ));
    std::cout << chordify(MYARG_USE_FOO, MYARG_USE_BAZ, MYARG_USE_BLAH);
}

输出 13,它是一个编译时常量。