Boost logo

Boost :

Subject: Re: [boost] Checking interest: reflectable types
From: Larry Evans (cppljevans_at_[hidden])
Date: 2011-04-28 14:23:14


On 04/27/11 18:23, Noah Roberts wrote:
> 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.

I did understand that a fieldI was not what was actually stored and
that, instead, a FieldI::value_type was stored. The only reason for
using "FieldI const" instead of "FieldI,r" as the template arguments
to var was just to eliminate the need for more "type flags", such as r
and rw. However, now that I've thought about it some more, that seems
a minor point.

[snip]
>>
>>> >::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.

So, params is bassed to each FieldI type and that FieldI type decides
which of the params to use to initialize it's FieldI::value_type?
IOW, params has to contains all the information needed to initialize
all type types in:

   base_class
   < var< field0, rwFlag0>
   , var< field1, rwFlag1>
   , var< field2, rwFlag2>
   ...
   , var< fieldN, rwFlagN>
>

and somehow, the var or fieldI classes figure how to intialize from
the single params argument?

Some actual prototype code would help me understand better.

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

Ah, so maybe I should wait for that to have my last question answered?

>
>>> };
>>>
>>> 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.

My apologies. I should have read closer the code you provided.
Apparently, my_reflected_type2 is an extension of my_reflected_type
because it uses template, derive_from<my_reflected_type,...>. So, I
guess the base_class<V0,V1,V2,...>::type does an mpl::fold of
derive_from?

>
> You can initialize or not initialize any field you chose,

Understandable.

> thus named params rather than construction like tuple<> or others.

I don't understand "thus named params". Could you clarify?

> If you initialize with a set of params, the values they target will

How are "values targeted"? I guess that's done with the <= operator
you mention further down; however, that appears to use separate values
for each field; yet, params seems to be used to intialize each field.

Again, prototype code might clear things up.

> be thus initialized while those you don't supply will be default
> constructed. (so yeah, sort of has to do with my earlier question)

Did you have a chance to took at the code linked-to in my reply:

http://article.gmane.org/gmane.comp.lib.boost.user/67038

to your earlier question? That link to was a test driver, whose
relevant part was started with:

        trace_scope ts("all_of_aligned index_component DEMO");

The implementation tested in that part is here:

http://svn.boost.org/svn/boost/sandbox/variadic_templates/boost/composite_storage/pack/indexed_ctor_args_all_of_aligned.hpp

AFAICT, that code does what you describe, but with variadic
templates. Briefly, it does this starting from the variadic CTOR:

      template
      < index_type... Keys
      , typename... CtorTypes
>
    indexed_ctor_args_all_of_aligned
      ( index_component
          < index_type
          , Keys
          , CtorTypes
>const&...
        ctor_args
      )
      {
            args_ftor
            < index_component
              < index_type
              , Keys
              , CtorTypes
>...
>
          f_args
            ( this->address()
            , ctor_args...
            )
          ;
          mpl::for_each<typename layout_indices::indices>(f_args);
      }

The ctor_args are produced (as shown in the driver) with arguments
created with:

   index<IndexType,IndexValue>::operator=<Component>

defined here:

http://svn.boost.org/svn/boost/sandbox/variadic_templates/boost/composite_storage/index.hpp

I'd guess this index<,>::operator= plays a role similar to your <=
operator. The args_ftor::get_our is used with SFINAE and
args_ftor::inject to select whether to default construct the component
or copy construct the component. This is done by first passing 'this'
to the overloaded get_our in method:

          template
          < index_base IndexValue
>
          void
        operator()
          ( mpl::integral_c<index_base,IndexValue> a_index
          )const

Since '*this' is derived from all of:

  index_component<index_type,Keys,CtorTypes>...

either the selected component is found by the compiler and:

          template
          <...
>
          void
        inject
          ( index_component<index_type,Key,CtorType>const* a_arg
          )const

is called and the Component copy ctor is used to inject into the
components buffer, or the component is not found and:

          template
          <...
>
          void
        inject
          ( not_in_args*
          )const

is called and the default ctor is used to inject into the components
buffer. In each case the components buffer is populated using
placement new. This components buffer together with placement new
allows separation of reserving the memory for the components from the
initialization of components.

BTW, I think (although I've not tried at all to do this with fusion)
that by separating the component storage reservation from the
component initialization, accomplishing this:

> Doing correct base construction

would be a bit easier than with something like fusion which,
since it uses member variables, must do the storage allocation at
about the same time, i.e. in the same class, as the initialization.
That's one reason I'm interested in seeing how you will do this
(which, according to the completion of the above quote:

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

has not yet been done.).

Also, I'd think a non-variadic version could be done without
much trouble, in case you've no access to a variadic templates
compiler.

> 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).

Noah, I'm still not sure of the meaning of:

  the param will first initialize the value from your provided data

because I thought param was the provided data. Also, I'm unsure of
the meaning of:

  The type that feeds into a set of params

Could you please clarify?

[snip]

>>>
>>> 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));

Ah, now I see. This expression:

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

is an example of "Building records on the fly". I'm guessing you're
doing something like what proto does with expression template where
you define an operator <= to produce 1 element in something like a map
and then use the comma operator to augment that map.

>
> Records and param lists being nearly interchangeable in this regard.

So, the above expression which I copied from your code is what you
mean by a "param list"?

> 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() {}
>

OK, now things are getting clearer. The has_field in the above
corresponds roughly to the get_our mentioned above. The Params
corresponds to the superclasses of args_ftor. the
value(get<my_field>(p)) corresponds to:

          void
        inject
          ( index_component<index_type,Key,CtorType>const* a_arg
          )const

and the value() corresponds to:

          void
        inject
          ( not_in_args*
          )const

Just above, you say:

> I ended up with something more like:

However, earlier you say:

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

so, is this value_impl code not yet working, or do you mean something
else is not working when you say "base construction is one of the
to-be-solved things"?

>
> 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.

Do you mean the field is a pointer or reference to an 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.

Could you use CRTP?

http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

I think spirit uses this a lot.

> 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.

Hmmm. So 1) actually has a pointer, and the derived CTOR uses new to
create the pointer which it then passes to the base CTOR? And so 3)
actually has a field whose reference is passed to the CTOR of the
base?

>
> 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.
>

Ok, then that's beyond what the code I proposed is capable of,
AFAICT. So, as I said, I'd be interested in seeing how you do this.
Best of luck!

-regards,
Larry


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