M
Mark Piffer
Hi c.l.c,
after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor. I will roughly depict below
what I came up with:
DISCLAIMER: as the preprocessor symbols are heavily used in the code
and not everyone likes being constantly shouted at when reading
source, please mentally replace the all-caps notation with something
more friendly. And in case you come up with much nicer or more logical
names for the operations, I am all ears.
an iterator definition looks like this:
#define pp_itername
ITER_DEF(name,start,end,DEREF(deref_exp,NEXT(next_stmt,ATEND(atend_exp,....ITER_ENDDEF)))))
The pp_itername will subsequently be used in operations, e.g.
ITER_NEXT(pp_itername)
x = ITER_GET(pp_itername)
Of course, pp_itername will have vanished by the time the compilers
gets to see the code. So this looks a bit unusual as there is another
level of indirection involved on the symbolic level in comparison to C+
+ where the "name" object can be used directly. I doubt however that
there is a way to involve the central iterator variable in the
operations and still get the same functionality. In fact, the
indirection gives an awful lot of flexibility sometimes even over a C+
+ iterator.
Such an iterator #define will be used most likely inside a function
with local identifiers present which act as the frame of reference of
the iterator. The name,start,end symbols are identifiers or
expressions which are available in the scope where the iterator is
intended to be used. E.g.
void frobnicate_all_tids(struct TaskQ *taskq, int n)
{
int ix;
int j;
/* access all tid's inside one task queue which currently
happens to be implemented as a fifo with head "hd"
and tail "tl" - the fifo itself is located inside one large
array together with the other fifos, each being limited
there with relating "top" and "bottom" indices */
#define queue_j ITER_DEF(ix,taskq[j].hd,taskq[j].tl, \
next( ix=(ix==taskq_limit[j].top)?
taskq_limit[j].bottom:ix+1, \
atend( ix==taskq[j].tl, \
deref( tid_queues[ix], \
ITER_ENDDEF))))
for(i=0;i<n;i++) {
ITER_INIT(queue_j);
while(!ITER_ATEND(queue_j)) {
frobnicate(ITER_GETN(queue_j));
}
}
#undef queue_j
}
The idea was to remove the awkward procedural work of accessing
awkward data structures out of such otherwise simple straightforward
algorithms. One can argue of course, that this is a failure in
encapsulation, but for the moment please just stay with me. As a plus,
the ITER_DEF macro is defined in such a way that one doesn't have to
supply a complete set of operations ( the shortest form would be
ITER_DEF(name,start,end,ITER_ENDDEF) ). A list of properties and
operations follows:
Basic Properties (must be supplied):
name...expression which is used to access (assign, dereference)
start...start expression, must evaluate to an assignable value
end...end expression, like start
Internal operations (can but don't have to be supplied):
[op. name] [default value] [explanation]
"TYPE" ... unsigned char ... type expression, element type
"DEREF" ... *name ... expression, read access (rhs)
"ASSIGN" ... *name ... expression, write access (lhs)
"ATEND" ... name >= end ... bool/int expression, end test
"INIT" ... name = start ... statement, move to start
"ENDEXN" ... {} ... statement, end exception (on
access)
"NEXT" ... ++name ... statement, move to next elem.
"PREV" ... --name ... statement, move to prev. elem.
"ADVANCE" ... name=+n_advance ... statement, move to n-th next elem.
"DISTANCE" ... end-name ... size_t expression, distance to end
Interface operations:
ITER_INIT // intitialization (name = start)
ITER_TYPE // iterator element type for defining temporaries
"ITER_TYPE(my_iter) tmp; tmp = ITER_GET(my_iter);"
ITER_GET // ...
ITER_PUT // ...
ITER_GETN // get and next
ITER_PUTN // put and next
ITER_ATEND // test on end
ITER_NEXT // advance one element
ITER_PREV // go back one element
ITER_UNGET // put back element
ITER_SKIP // advance more than 1 element
Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?
regards,
Mark
PS: yes, Boost has some nice facilities on their site (I think it's
called boost_pp or something) but I admit I never groked their method.
after reading through a sufficient part of the iterator-thread, I
remembered an iterator-like preprocessing facility which I wrote a
while back. The aim was to provide zero-overhead ad-hoc iterator
functionality through the C preprocessor. I will roughly depict below
what I came up with:
DISCLAIMER: as the preprocessor symbols are heavily used in the code
and not everyone likes being constantly shouted at when reading
source, please mentally replace the all-caps notation with something
more friendly. And in case you come up with much nicer or more logical
names for the operations, I am all ears.
an iterator definition looks like this:
#define pp_itername
ITER_DEF(name,start,end,DEREF(deref_exp,NEXT(next_stmt,ATEND(atend_exp,....ITER_ENDDEF)))))
The pp_itername will subsequently be used in operations, e.g.
ITER_NEXT(pp_itername)
x = ITER_GET(pp_itername)
Of course, pp_itername will have vanished by the time the compilers
gets to see the code. So this looks a bit unusual as there is another
level of indirection involved on the symbolic level in comparison to C+
+ where the "name" object can be used directly. I doubt however that
there is a way to involve the central iterator variable in the
operations and still get the same functionality. In fact, the
indirection gives an awful lot of flexibility sometimes even over a C+
+ iterator.
Such an iterator #define will be used most likely inside a function
with local identifiers present which act as the frame of reference of
the iterator. The name,start,end symbols are identifiers or
expressions which are available in the scope where the iterator is
intended to be used. E.g.
void frobnicate_all_tids(struct TaskQ *taskq, int n)
{
int ix;
int j;
/* access all tid's inside one task queue which currently
happens to be implemented as a fifo with head "hd"
and tail "tl" - the fifo itself is located inside one large
array together with the other fifos, each being limited
there with relating "top" and "bottom" indices */
#define queue_j ITER_DEF(ix,taskq[j].hd,taskq[j].tl, \
next( ix=(ix==taskq_limit[j].top)?
taskq_limit[j].bottom:ix+1, \
atend( ix==taskq[j].tl, \
deref( tid_queues[ix], \
ITER_ENDDEF))))
for(i=0;i<n;i++) {
ITER_INIT(queue_j);
while(!ITER_ATEND(queue_j)) {
frobnicate(ITER_GETN(queue_j));
}
}
#undef queue_j
}
The idea was to remove the awkward procedural work of accessing
awkward data structures out of such otherwise simple straightforward
algorithms. One can argue of course, that this is a failure in
encapsulation, but for the moment please just stay with me. As a plus,
the ITER_DEF macro is defined in such a way that one doesn't have to
supply a complete set of operations ( the shortest form would be
ITER_DEF(name,start,end,ITER_ENDDEF) ). A list of properties and
operations follows:
Basic Properties (must be supplied):
name...expression which is used to access (assign, dereference)
start...start expression, must evaluate to an assignable value
end...end expression, like start
Internal operations (can but don't have to be supplied):
[op. name] [default value] [explanation]
"TYPE" ... unsigned char ... type expression, element type
"DEREF" ... *name ... expression, read access (rhs)
"ASSIGN" ... *name ... expression, write access (lhs)
"ATEND" ... name >= end ... bool/int expression, end test
"INIT" ... name = start ... statement, move to start
"ENDEXN" ... {} ... statement, end exception (on
access)
"NEXT" ... ++name ... statement, move to next elem.
"PREV" ... --name ... statement, move to prev. elem.
"ADVANCE" ... name=+n_advance ... statement, move to n-th next elem.
"DISTANCE" ... end-name ... size_t expression, distance to end
Interface operations:
ITER_INIT // intitialization (name = start)
ITER_TYPE // iterator element type for defining temporaries
"ITER_TYPE(my_iter) tmp; tmp = ITER_GET(my_iter);"
ITER_GET // ...
ITER_PUT // ...
ITER_GETN // get and next
ITER_PUTN // put and next
ITER_ATEND // test on end
ITER_NEXT // advance one element
ITER_PREV // go back one element
ITER_UNGET // put back element
ITER_SKIP // advance more than 1 element
Although the facility works, I never dared to introduce or even
propose this to co-workers, mostly because they are frowning upon much
much (much) simpler preprocessor utilitzations. Where is your personal
pain threshold regarding meta-C aka preprocessor abuse ?
regards,
Mark
PS: yes, Boost has some nice facilities on their site (I think it's
called boost_pp or something) but I admit I never groked their method.