views:

164

answers:

5

The goal is to control which types of users are allowed to perform which operations at the UI level. This code has been in place for a while; I just want to improve it a bit. The file which I am trying to improve should probably be auto-generated, but that would be too big of a change, so I seek a simpler solution.

A file which we shall call PermissionBits.h has a bunch of these:

// Here names are mangled; for example XYZ_OP_A is:
// permission to operation A in category/context XYZ
// SCU64 = static const unsigned __int64
// Some namespaces utilize all 64 bits
// The actual values (as long as they are proper bit fields) 
// do not matter - they are always used by name
namespace XYZPermissionBits
{
    SCU64 XYZ_OP_A = 1UI64 <<  0; //    1 = 0x0000000000000001
    SCU64 XYZ_OP_B = 1UI64 <<  1; //    2 = 0x0000000000000002
    SCU64 XYZ_OP_C = 1UI64 <<  2; //    4 = 0x0000000000000004
    SCU64 XYZ_OP_C = 1UI64 <<  3; //    8 = 0x0000000000000008
    SCU64 XYZ_OP_D = 1UI64 <<  4; //   16 = 0x0000000000000010
    SCU64 XYZ_OP_E = 1UI64 <<  5; //   32 = 0x0000000000000020
    SCU64 XYZ_OP_F = 1UI64 <<  6; //   64 = 0x0000000000000040
    SCU64 XYZ_OP_G = 1UI64 <<  7; //  128 = 0x0000000000000080
    SCU64 XYZ_OP_H = 1UI64 <<  8; //  256 = 0x0000000000000100
    SCU64 XYZ_OP_I = 1UI64 <<  9; //  512 = 0x0000000000000200
    SCU64 XYZ_OP_J = 1UI64 << 10; // 1024 = 0x0000000000000400
    SCU64 XYZ_OP_K = 1UI64 << 11; // 2048 = 0x0000000000000800
    SCU64 XYZ_OP_L = 1UI64 << 12; // 4096 = 0x0000000000001000 
}

Even with the help of 1UI64 << <numBits>; shortcut there are still problems, as coders create flags with duplicate values, make typos, etc.

Ideally I would like a macro which can be nicely formatted and look like:

BITFIELDS_FOR_NAMESPACE(
    //*************** <<== I want to make the namespace name more vivid
    XYZPermissionBits,
    //*************** <<== somehow if that is possible. It is not a must-have.
    XYZ_OP_A, // Being able to add a comment here would be nice, but not critical
    XYZ_OP_B,
    XYZ_OP_C,
    XYZ_OP_D,
    XYZ_OP_E,
    XYZ_OP_F,
    XYZ_OP_G,
    XYZ_OP_H,
    XYZ_OP_I,
    XYZ_OP_J,
    XYZ_OP_K,
    XYZ_OP_L
)

I would like this macro be flexible and prevent me from entering less than 2 or more than 65 arguments - namespace name + 64 flags. Is it possible to do what I want or close to it, or should I resort to generated code? What other advice do you have?

+2  A: 

IFAIK, The boost preprocessor library http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html has all the primitives you need.

AProgrammer
An example would be great. Can I just steal a header file from that library, or do I have to install something? Is it still a standard preprocessor or some other magic? I am using VS2010 on Windows.
Hamish Grubijan
Without Boost Preprocessor, it would take a large amount of nearly-identical macros, which would be even more error prone than your original problem unless you used code generation to create them...
Cogwheel - Matthew Orlando
Boost Preprocessor is entirely implemented with headers. You should be able to incorporate them directly into your project, but Boost has so many other goodies it may be worth keeping the whole thing around.
Cogwheel - Matthew Orlando
@cog, about your second comment - so ... what is different about boost preprocessor? Would not it have to create thousands of Macros, or that's how it is done, with the exception that these macros have been in use and heavily tested?
Hamish Grubijan
@Hamish, boost is standard code. While it sometimes push the compilers to their limits it often contains work around or offer simply degraded functionalities for non conforming compilers, especially mainline one. And I don't think that part is pushing the limits very hard, it is just tedious code.
AProgrammer
@Hamish, your intuition is correct. If you take a look at the boost code, it's incredibly tedious. It's been around quite a while and is well-tested though, so by using it you're taking advantage of all that already-invested time.
Cogwheel - Matthew Orlando
+2  A: 

If you do decide to go the code generation route then I suggest taking a look at Cog.

Cog lets you embed python code as comments in the C++ (or any other language) source file, and when run through Cog the python output is inserted as source code, so the generator code and generated output are all managed in the same file. This keeps maintenance simple. and the python code documents how the generated code was created.

Here is your example using cog, including the generated code:

namespace XYZPermissionBits
{
    /* [[[cog
    import cog
    operations = ["A", "B", "C", "D", 
                  "E", "F", "G", "H",
                  "I", "J", "K", "L"]

    assert 2 <= len(operations) <= 64

    for idx,op in enumerate(operations):
        cog.outl("SCU64 XYZ_OP_%s = 1UI64 << %s;" % (op, idx))

    ]]] */
    SCU64 XYZ_OP_A = 1UI64 << 0;
    SCU64 XYZ_OP_B = 1UI64 << 1;
    SCU64 XYZ_OP_C = 1UI64 << 2;
    SCU64 XYZ_OP_D = 1UI64 << 3;
    SCU64 XYZ_OP_E = 1UI64 << 4;
    SCU64 XYZ_OP_F = 1UI64 << 5;
    SCU64 XYZ_OP_G = 1UI64 << 6;
    SCU64 XYZ_OP_H = 1UI64 << 7;
    SCU64 XYZ_OP_I = 1UI64 << 8;
    SCU64 XYZ_OP_J = 1UI64 << 9;
    SCU64 XYZ_OP_K = 1UI64 << 10;
    SCU64 XYZ_OP_L = 1UI64 << 11;
// [[[end]]]
}
Dave Kirby
Wait, what you posted is a union of source and result, or is cog "self-modifying", if that makes sense?
Hamish Grubijan
@Hamish: yes, cog is self modifying as you put it - the cog output includes both the original file contents and the generated code. See the cog documentation at the link I posted.
Dave Kirby
+4  A: 

Tested example using Boost.PreProcessor:

#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/comparison/greater.hpp>
#include <boost/preprocessor/comparison/less.hpp>
#include <boost/preprocessor/debug/assert.hpp>
#include <boost/preprocessor/seq/size.hpp>

#define CHECK_SIZE(size) \
  BOOST_PP_ASSERT_MSG(BOOST_PP_GREATER(size, 1), "<  2 :(") \
  BOOST_PP_ASSERT_MSG(BOOST_PP_LESS(size, 65),   "> 64 :(") \

#define DO_MAKE_BITFIELDS(a, b, i, elem) \
  SCU64 elem = 1UI64 << i;

#define BITFIELDS_FOR_NAMESPACE(name, seq) \
  CHECK_SIZE(BOOST_PP_SEQ_SIZE(seq)) \
  namespace name { \
    BOOST_PP_SEQ_FOR_EACH_I(DO_MAKE_BITFIELDS, _, seq) \
  }

Usage:

BITFIELDS_FOR_NAMESPACE(
    XYZPermissionBits,
    (XYZ_OP_A)
    (XYZ_OP_B)
    // ...
);
Georg Fritzsche
@Georg, if you could actually test this, it would be so awesome!
Hamish Grubijan
@Hamish: Did a quick test and corrections.
Georg Fritzsche
Thanks, just one clarification: why do you need a comma after a namespace name but not after flag names? Are parenthesis also defined as macros :)?
Hamish Grubijan
@Ham: The namespace name is the first argument, the comma seperates it from the second argument: all the parantheses-enclosed flag names form one argument and are a [sequence](http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/data/sequences.html), which can then be processed with the `BOOST_PP_SEQ_*`.
Georg Fritzsche
@Ham: Added a short [blog post](http://colonelpanic.net/2010/07/boost-preprocessors-sequences/) on those sequences if you're interested.
Georg Fritzsche
A: 

Instead of macros/preprocessors/etc I'd stick with a static text file similar to the original. It seems much easier to understand and expand or maintain. I might define each value as a shift of the previous value, but that might seem more confusing to some readers. To help prevent typos by users, I'd probably use a more verbose abbreviation for each "op" instead of A, B, C (defined twice), D...

joe snyder
@Hamish: 1000 flags! is it logically just every possible combination of operation and context? perhaps editing your original question into a more complete description of the who/what/when/where/why/how of your system will let someone suggest an even better design or solution...
joe snyder
Hamish Grubijan
A: 

I've done something like this. Create a macro called MAKE_THINGS that looks like:

#define MAKE_THINGS \
  MAKE_THING(NAME1) \
  MAKE_THING(NAME2) \
  MAKE_THING(NAME3) \
  /* Include semantically-blank line after list */

One can then define MAKE_THING, start a declaration, call MAKE_THINGS, end the declaration, and undefine MAKE_THING. If desired, each thing can include more than one attribute:

For example:

#define MAKE_THINGS \
  MAKE_THING(HAPPY,"Happy") \
  MAKE_THING(SAD_AND_BLUE,"Sad and blue") \
  MAKE_THING(SLEEPY,"Sleepy") \
  /* Include semantically-blank line after list */

#define MAKE_THING(x,y) NUMBER_##x,
typedef enum {MAKE_THINGS LAST_THING} THING_NUMBER;
#undef MAKE_THING

#define MAKE_THING(x,y) FLAG_##x = (1L << NUMBER##x),
typedef enum {MAKE_THINGS ALL_FLAGS = (1 << LAST_THING)-1} THING_FLAGS;
#undef MAKE_THING

#define MAKE_THING(x,y) const char *MSG_##x = y;
MAKE_THINGS
#undef MAKE_THING

#define MAKE_THING(x,y) MSG_##x,
const char *thing_names[] = {MAKE_THINGS 0};
#undef MAKE_THING

Note that all of the declarations are automatically kept parallel. Each thing gets a number (0..N-1), an flag (bit corresponding to its number), a message string, and a place in an array of message strings.

supercat