3

I know this question may be very vague, and it's a bit of an extension to an answer I gave here. Basically, as a personal project I have been trying to replicate the "code-first" idiom, that is popular with C# programmers doing database-work, but in C++. Everything is great, and works well.

One of the great things about C# is things such as attributes. We don't have that novelty in C++, so my code for a particular class that is then structured in the database, looks like this:

class User : public Record {
public:
    User(Model& m) : Record(L"_user", m)
                   , Username(L"_userName", 32, false)
                   , Nickname(L"_nickName", 32, false) {
        field(Username);
        field(Nickname);
    };  // eo ctor

    Field<String> Username;
    Field<String> Nickname;
};  // eo class User

Ideally, I want to get rid of the field calls in the constructor. All they do, essentially, is register the field with the base class which then does the real work. How might I accomplish this kind of thing, and pass the details to the base class as simply as I have specified in the constructor?

In a previous question that I answered I was able to give the base class information thanks to the way C++ constructs things. Is there a way I can leverage this in a constructor and get rid of the pesky field calls that may introduce programmer error?

At this point I am happy that everything works wonderfully, but I'd love to cut down on clutter and have remove the need for this "registration" process in the constructors and somehow have them registered automatically with the base class.

All opinions welcomed :)

EDIT

The implentation of field is thusly:

        void Record::field(detail::field_base& field) {
            field.owner_ = this;
            fields_.push_back(&field);
        };  // eo field**
Community
  • 1
  • 1
Moo-Juice
  • 38,257
  • 10
  • 78
  • 128
  • 1
    It would help if you could show what `field()` actually does and which class it is a member of (`Record`, I guess, but it's just a guess) – Andy Prowl Mar 14 '13 at 00:05
  • @AndyProwl, I have amended the question. It does very little other than push it in to a list for referral later. – Moo-Juice Mar 14 '13 at 00:08

3 Answers3

4

You could have the constructor of Field<> accept an additional pointer to a Record and invoke field(*this) on that pointer in the constructor's body.

template<typename T>
struct Field // Just guessing...
{
    Field(std::string s, int i, bool b, Record* pRecord) 
    // ...
    { 
        pRecord->field(*this); 
    }
    // ...
};

Then, in your initialization list of User you would pass this as the additional Record pointer, this way:

User(Model& m) : Record(L"_user", m)
               , Username(L"_userName", 32, false, this)
               , Nickname(L"_nickName", 32, false, this)
{
};  // eo ctor
Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • Wow, I may be missing something but... I had it in my head that `this` was invalid during the initialization part... this may well have been my mistake, of course. – Moo-Juice Mar 14 '13 at 00:15
  • 1
    @Moo-Juice: The base sub-object has been constructed by the time the constructor of `Record` gets invoked (members are constructed after base classes), so this should be well-formed. – Andy Prowl Mar 14 '13 at 00:17
  • @Moo-Juice Be careful about invoking functions that are `virtual` during construction (even indirectly!). And avoid do things that can fail unless you cannot help it (handling failure during construction is annoying) – Yakk - Adam Nevraumont Mar 14 '13 at 01:48
2

just pass the requisite information, namely your this pointer, to the Field constructor, and let it do the work

you might even introduce a special wrapper class for this

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
2

Don't get hung up on attribute syntax.

What you want is the ability to treat structs as aggregates of meaningfully named data with runtime reflection, correct?

To introduce new names, use types:

template<typename T> struct FieldTag {};
struct Username:FieldTag<Username> {} username;
struct Nickname:FieldTag<Nickname> {} nickname;

To produce an aggregate of types, use template arguments:

template<typename Child, typename Field>
struct FieldHandler {
  typedef typename Field::tag_type tag_type;
  typedef typename Field::value_type value_type;
  PsuedoRef<value_type> operator[]( tag_type ) { /* details */ }
  /* details */
};
template<typename... Fields>
struct Aggregate:
  Record,
  FieldHandler<Aggregate<Fields...>, Fields>...
{
  /* details */
};
template<typename Value, typename Tag>
struct Field {
  typedef Tag tag_type;
  typedef Value value_type;
};

The goal of all the above boilerplate, plus some operator override abuse, is that we can get some beautiful syntax out of this, and the end programmer gets a syntax that looks a lot like C++ without boilerplate.

Here is the end goal for how you'd create a Data type with two fields:

struct Data:
  Aggregate<
    Field<std::string, Username>,
    Field<std::string, Nickname>
  >
{
  Data(): Aggregate(
    username|L"username" = L"Unknown Name",
    nickname|L"nickname" = L"Unknown NickName"
  {}
};

To access a field, use suitably overriden operator: (I like ^, it binds tightly, and looks sort of like ->)

Data d;
d^username= L"Bob";
d^nickname= L"Apples";

This is all possible in C++11, but it isn't all that easy.

Now, the ability to take pointers to functions as template arguments, together with stateless lambdas being convertible to functions, might let us store the string in the type of the field, doing away with the need to pass it in at construction time. On the other hand, restrictions against unevaluated context makes that seem questionable.

So that is one ugly bit remaining. Plus, the effort required to get the above working makes me think you probably should just swallow the annoying boilerplate on each field.

Note the the above code is just pseudo code, and a sketch of a route you can take, and not all that close to being complete. I'd advise against trying to go down this route unless you want to get lost in the woods of learning the template metaprogramming sublanguage of C++ and never get around to finishing your problem.

Then again, wouldn't learning template metaprogramming be more fun than your current project? ;)

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524