Boost logo

Boost :

Subject: Re: [boost] Checking interest: reflectable types
From: Noah Roberts (roberts.noah_at_[hidden])
Date: 2011-04-27 19:23:56


On 4/27/2011 1:06 PM, Larry Evans wrote:

>>
>> Use of mine is more like so:
>>
>> struct field0 { typedef int value_type; };
>> struct field1...2 ...
>>
>> struct my_reflected_type
>> : build_class
>> <
>> var< field0, rw>
>> , var< field1, r> // can't assign through reflection.
> Why not:
>
> var< field>
> , var< field const>
>
> instead? I guess I'd have to see the code to really
> understand why the extra type parameter is needed,
> but maybe a short explanation would be enough.

I suppose that'd be an interesting method of doing it. The field itself
though isn't the type stored so the const would have to be applied to
that value or applied in the same way I end up using the access rights.

Currently though in a reflected type containing a read-only field the
value itself isn't actually read only. It may be that the class itself
may implement other behavior that changes the value. It just means that
the value is read only through the reflection API. Thus when I say
"get<field>(object)" I get a const reference if the field is
non-writable or object is const. If, on the other hand, I violate the
API through casting (which is more normal from inside the class) I can
write to that value from a non-const member.

>
>> >::type
>> {
>> virtual ~my_reflected_type(){}
>> DECLARE_PARAM_CONSTRUCTOR(my_reflected_type);
> Could you provide the source code for:
>
> DECLARE_PARAM_CONSTRUCTOR
>
> to give us some idea of what it's doing?

template < typename Params >
name (Params const& params) : type(params) {}

It just saves time. The type you inherit from (which declares itself as
type inside itself like a lot of meta-objects) has two constructors.
One is the default, the other is just like the above and forwards the
params to the value implementation objects to either accept or discard.
  A lot of times you won't care to make one of these yourself, but
instead make a constructor that uses params to initialize. It's
currently important for base classes though so derived classes can
forward params.

Doing correct base construction is one of the to-be-solved things.

>> };
>>
>> struct my_reflected_type2
>> : derive_from
>> <
>> my_reflected_type
>> , var< field2, rw>
>> >::type
>> {
>> my_reflected_type2()
>> : type(( field0()<= some_val
>> , field1()<= some_val
>> , field2()<= some_val ))
>> {
>> // directly access read-only value and assign...
>> var<field1,r>::value = 66.;
>> }
>> };
>
> Hmm... does the user have to repeat this some number of
> times, same the number of fields in the my_reflected_type?
> IOW, for I=3...N (where N is number of fields in my_reflected_type):
>
> my_reflected_typeI
> {
> //repeat like my_reflected_type2
> };
>
> Or is this done by the macro using boost,preprocessor library?
> I would guess the latter because providing all this boilerplate
> code would seem too much to me.

You can initialize or not initialize any field you chose, thus named
params rather than construction like tuple<> or others. If you
initialize with a set of params, the values they target will be thus
initialized while those you don't supply will be default constructed.
(so yeah, sort of has to do with my earlier question)

If your value can't be default constructed you HAVE to provide a param
for it. It then has to be copy constructable for the param will first
initialize the value from your provided data. The type that feeds into
a set of params is specifically the value of the field in other words
(though I'm sure this could be changed, thinking of it now).

If your value is default constructible it need not be copy
constructible. If it is not, it'll have to be initialized to some
reasonable value outside the API and you won't be able to assign to it
through reflection.

The current state doesn't do functions though I have ideas there. We're
currently calling them "records".
>
>>
>> I also have a ref<> so bases can have fields to abstract types.
>>
>> So far functions haven't been needed here so there's none but I think
>> I have a handle on how that could be done.
>>
>> All of the above can be treated as a fusion sequence and/or fusion
>> associative sequence (including the param list).
>>
>> Building records on the fly with field()<=value syntax was very
>> desired. Comes in handy when you have a function expecting a record
>> with x,y,z fields in it, have those values, but don't want to build
>> the record class.
> I don't understand that. Could you be more concrete in describing
> this use case, maybe by providing a concrete example?

So for instance:

template < typename Record >
void fun(Record const& rec)
{
   static_assert(has_field<Record, what_i_need>::value, "Supplied record
doesn't have the necessary data!");

   .....
}

I can call this function like so:

what_i_need::value_type value;
int something_else;

fun(( what_i_need() <= value
     , int_field() <= something_else));

Records and param lists being nearly interchangeable in this regard.
Only showing the int_field() because it can happen...the function simply
ignores what it doesn't know about.
>
> ----------------------------
> BTW, a previous post of your's to the users list:
>
> http://thread.gmane.org/gmane.comp.lib.boost.user/66818/focus=67038
>
> seems related to your:
>
> field()<=value
>
> syntax. Is it?

Sort of. It's more about initializing the values themselves with the
params. I ended up with something more like:

template < typename Params >
value_impl(Params & p, typename boost::enable_if<
has_field<Params,my_field> >::type * = 0)
   : value(get<my_field>(p)) {}

template < typename Params >
value_impl(Params & p, typename boost::disable_if<
has_field<Params,my_field> >::type * = 0)
   : value() {}

I don't have rights to the code though I do the idea. If I did it I'd
need to address a few things:

* correct base initialization. With reflected types though your "base"
is not your direct, actual base so that becomes an issue.

* Better implementation of fields represented by abstract types.

Currently there's three approaches:
1) have the field in the base and make it refer to the abstract type.
2) have the field in the derived and make it refer to the most derived
type. Of course now you don't have access to it from the base.
3) Put one field in the base and a different field in the derived. Make
the base's a "reference" field. Does the trick but it's hacky.

I really want the best of both worlds. Put the field in the base to the
abstract type. When I request the field from a derived record have it
return the derived type. In other words:

struct base_field_type;
struct derived_field_type;

struct field { typedef base_field_type value_type; };

derived d;
base * b = &d;

decltype(get<field>(d)) == derived_field_type&
decltype(get<field>(*b)) == base_field_type&

* functions. I imagine something like so:

struct reflected
   : build_class
     <
       fun<fieldx, void(int)>
>::type
{
   void call(fieldx, int) { .... }
};

reflected r;
call<fieldx>(r, 42); // maybe call<fieldx>(r)(42);

I'm not sure how much actual value this has though. Personally I don't
have much use for it. On the other hand, I do have use for "fields"
implemented by functions such that:

int x = get<fieldx>(r);

is actually a call to a function rather than reference to a variable in r.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk