Re: [boost] Re: Formal Review: FOREACH macro

In-Reply-To: <20050430145039.A20350@titan.hansons.demon.co.uk> ikh@hansons.demon.co.uk (Iain K. Hanson) wrote (abridged):
I really don't like macros.
I too have a strong antipathy to macros. I don't reject them absolutely, but they need a very good justification. I can't see myself ever using BOOST_FOREACH because the problem it is solving isn't hard enough to warrant the obfuscation. Comparing: BOOST_FOREACH( int i, vec ) cout << i; with: for (iterator i = vec.begin(); i != vec.end(); ++i) cout << *i; the macro just doesn't seem worth it. I find it scary. When I look at the implementation I am not reassured. This is replacing something I understand with something I frankly don't. I have looked at the documentation and found it a bit superficial. For example, I can't tell whether the example above is actually supported. All the examples in the documentation have braces around the loop body: are those just the author's preferred style, or are they required by the macro? I looked at the implementation and I still can't tell. I prefer to omit braces when they are not required. I have not tried to use the macro. From the source, it looks like it injects a largish number of local variables into the code, and these will show up in the debugger and need to be understood to follow how the loop is progressing. For what it's worth, I think the design is impressive and if I had come up with it I'd be very proud. Congratulations. The problem is that it is too clever. It is using a sledgehammer to crack a nut. I am not sure how one is supposed to vote in these circumstances - for a proposed library that one wouldn't use oneself. I don't necessarily want to prevent other people from using it if they want. I think it could help demonstrate both the usefulness of type inference and how yucky type inference is to achieve in C++ at present. It is an argument for language change. However, I guess it doesn't need to be part of Boost to do that. I vote against accepting BOOST_FOREACH into Boost. I hope the Review Manager pays more attention to my reasons than my vote. -- Dave Harris, Nottingham, UK.

Dave Harris wrote:
I too have a strong antipathy to macros. I don't reject them absolutely, but they need a very good justification. I can't see myself ever using BOOST_FOREACH because the problem it is solving isn't hard enough to warrant the obfuscation. Comparing:
BOOST_FOREACH( int i, vec ) cout << i;
with:
for (iterator i = vec.begin(); i != vec.end(); ++i) cout << *i;
the macro just doesn't seem worth it.
In this simple example, I might agree with you. But see my reply to Gennadiy for an example of something that BOOST_FOREACH can do that an ordinary for loop, or even std::for_each, cannot.
I have looked at the documentation and found it a bit superficial. For example, I can't tell whether the example above is actually supported. All the examples in the documentation have braces around the loop body: are those just the author's preferred style, or are they required by the macro? I looked at the implementation and I still can't tell. I prefer to omit braces when they are not required.
Braces around the loop body are not required. I could add that to the docs.
I have not tried to use the macro. From the source, it looks like it injects a largish number of local variables into the code, and these will show up in the debugger and need to be understood to follow how the loop is progressing.
It injects 5 local variables. I haven't checked, but I suspect that some can be optimized away sometimes. I'm not sure why users will need to understand how FOREACH uses its hidden variables. Can you explain?
For what it's worth, I think the design is impressive and if I had come up with it I'd be very proud. Congratulations. The problem is that it is too clever. It is using a sledgehammer to crack a nut.
This nut is an exceptionally tough one to crack. ;-) -- Eric Niebler Boost Consulting www.boost-consulting.com

For what it's worth, I think the design is impressive and if I had come up with it I'd be very proud. Congratulations. The problem is that it is too clever. It is using a sledgehammer to crack a nut.
It allows most importantly: 1. Not to mention the collection type As Eric pointed out - you may not know one. But even if you do writing std::vector<mytypes<my_param> > several types for every loop I need is tidies. 2.No need to know/mention about iterator Since you agree that looping is quite basic need - simplifying it is a worthy task. As for the complexity it's required to support some corner cases situation. You could always disable it with defines (that what I am going to do). Gennadiy

Dave Harris wrote:
Comparing:
BOOST_FOREACH( int i, vec ) cout << i;
with:
for (iterator i = vec.begin(); i != vec.end(); ++i) cout << *i;
the macro just doesn't seem worth it.
In fairness, it's for (vector<ArbitrarilyComplexType>::iterator i = vec.begin(); i != vec.end(); ++i) cout << *i; And add to that that for is more general (in potentially harmful ways) because the iterator can be freely modified inside the loop. Often, that's not what you wanna. I don't like macros myself (ack! chaos! nooooo!...), but IMHO it's unfair to label FOREACH as macro-heavy machinery. The macro is but one layer behind which there's good non-macro stuff. I think the part of the discussion that focuses on arguments for or against macros is misplaced. We should ask ourselves about abstraction. That's the good thing. Does BOOST_FOREACH offer a good abstraction? Dave Harris says it's not enough, and I think that's the proper focus of discussion. (As a contrast, think of the boost initialization library. I doesn't use macros but I consider it way more dangerous, less useful, and offering less of an abstraction.) I think BOOST_FOREACH does offer a good, useful abstraction, that the disadvantages ot it using a macro are assuaged by good handling of macro-related problems and good taste in design, so if I have voting power around here, I'd vote for :o). Andrei

Andrei Alexandrescu (See Website For Email) wrote:
Dave Harris wrote:
Comparing:
BOOST_FOREACH( int i, vec ) cout << i;
with:
for (iterator i = vec.begin(); i != vec.end(); ++i) cout << *i;
the macro just doesn't seem worth it.
In fairness, it's
for (vector<ArbitrarilyComplexType>::iterator i = vec.begin(); i != vec.end(); ++i) cout << *i;
And add to that that for is more general (in potentially harmful ways) because the iterator can be freely modified inside the loop. Often, that's not what you wanna.
I don't like macros myself (ack! chaos! nooooo!...), but IMHO it's unfair to label FOREACH as macro-heavy machinery. The macro is but one layer behind which there's good non-macro stuff.
I think the part of the discussion that focuses on arguments for or against macros is misplaced. We should ask ourselves about abstraction. That's the good thing. Does BOOST_FOREACH offer a good abstraction? Dave Harris says it's not enough, and I think that's the proper focus of discussion. (As a contrast, think of the boost initialization library. I doesn't use macros but I consider it way more dangerous, less useful, and offering less of an abstraction.) I think BOOST_FOREACH does offer a good, useful abstraction, that the disadvantages ot it using a macro are assuaged by good handling of macro-related problems and good taste in design, so if I have voting power around here, I'd vote for :o).
Of course you have ;) I *used* to hate macros. The world would be a better place without them. They don't have scope, convention requires them to be in ugly ALL_CAPS, and prefix them with something like BOOST_ to avoid name clashes. There are obscure gotchas like not being able to use templates with commas, they need those yucky backslashes when you have to define multi-line macros, they are a pain to debug (an error in macro code takes you to the macro incocation, not the macro itself), etc, etc. I *used* to believe that someday, macros will be replaced by true c++ language facilities. "inline" code was a remarkable first start. I *used* to believe that the more we let macros proliferate, the more difficult it will be to dismantle this ugly relic of the past when that time comes. Now I question that. Why don't we simply improve the PP instead? Sure, macros, and the PP in general, are severely problematic. Let's fix them! The boost PP made me realize that macros *are* good when used appropriately and intelligently. It also made me realize that there are things we can do now that is not possible, or at least not practical to do without macros. FOREACH is one of them. *** I vote *yes* to accepting BOOST_FOREACH into boost. *** Someday, when we have an improved PP, we will have a more potent tool to use for writing libraries and facilities. Macros are a simple yet powerful code generation mechanism. Facilities like what's BOOST_FOREACH now, can behave like real language extensions. With proper PP scopes (or maybe namespaces), we can get rid of the ugly ALL_CAPS and BOOST_ prefix naming conventions. Then, it can be spelled simply as 'foreach'. A library based facility is always preferrable over a language extension. Cheers, -- Joel de Guzman http://www.boost-consulting.com http://spirit.sf.net

-----Original Message----- From: boost-bounces@lists.boost.org [mailto:boost-bounces@lists.boost.org] On Behalf Of Andrei Alexandrescu (See Website For Email)
I don't like macros myself (ack! chaos! nooooo!...), but IMHO ^^^^^ All that I can do here is smile.
I think the part of the discussion that focuses on arguments for or against macros is misplaced. We should ask ourselves about abstraction.
I'll reiterate something that you said a while back (paraphrasing). The greatest harm that the macro mechanism (in C/C++) has caused is that it has kept people from realizing the benefits of syntactic abstraction. Furthermore, whether something is or isn't a macro is irrelevant. It is whether it provides a useful abstraction that is documentable. As a macro, the name is not going to collide with anything because of all-caps and the BOOST_ prefix. Similarly, it isn't going to accidentally expand in any code. As I've said before (paraphrasing), macro invocations are nothing like function calls except in appearance, and nearly all other macro-related problems stem from developers viewing them as if they are. Macros' "return values" are code snippets, not values. E.g. #define max(a, b) ((a) < (b) ? (b) : (a)) The problem here is not that 'max' has a problem with multiple evaluation; the problem is that typical developers view 'max' as returning either 'a' or 'b'. In other words, whether an abstraction is a macro is an important documentation point, and the documentation of an abstraction, regardless of what it is (macro, function, class, etc.), dictates the entirety of how it is to be used. Regarding BOOST_FOREACH specifically: I think that it provides a useful abstraction, and it costs very little in terms of source. As such, it is a good utility to have around. Regards, Paul Mensonides

Paul Mensonides wrote:
I'll reiterate something that you said a while back (paraphrasing). The greatest harm that the macro mechanism (in C/C++) has caused is that it has kept people from realizing the benefits of syntactic abstraction.
Furthermore, whether something is or isn't a macro is irrelevant. It is whether it provides a useful abstraction that is documentable. As a macro, the name is not going to collide with anything because of all-caps and the BOOST_ prefix. Similarly, it isn't going to accidentally expand in any code. As I've said before (paraphrasing), macro invocations are nothing like function calls except in appearance, and nearly all other macro-related problems stem from developers viewing them as if they are. Macros' "return values" are code snippets, not values. E.g.
#define max(a, b) ((a) < (b) ? (b) : (a))
The problem here is not that 'max' has a problem with multiple evaluation; the problem is that typical developers view 'max' as returning either 'a' or 'b'. In other words, whether an abstraction is a macro is an important documentation point, and the documentation of an abstraction, regardless of what it is (macro, function, class, etc.), dictates the entirety of how it is to be used.
Agreed. In the case of max, it's the syntactic similarity with a function call that lures people into making certain assumptions. Same goes about non-macro entities such as auto_ptr - "a = b;" looks like something that ain't. By the way, it was FOREACH that inspired me to figure out a simple solution to max, which then Eric improved. It's basically like this: #define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b))) which presto, nice-o passes the appropriate types to a template max_fn function that now can be smart about returning lvalues and so on. Andrei

Andrei Alexandrescu (See Website For Email) wrote:
Paul Mensonides wrote:
I'll reiterate something that you said a while back (paraphrasing). The greatest harm that the macro mechanism (in C/C++) has caused is that it has kept people from realizing the benefits of syntactic abstraction.
Furthermore, whether something is or isn't a macro is irrelevant. It is whether it provides a useful abstraction that is documentable. As a macro, the name is not going to collide with anything because of all-caps and the BOOST_ prefix. Similarly, it isn't going to accidentally expand in any code. As I've said before (paraphrasing), macro invocations are nothing like function calls except in appearance, and nearly all other macro-related problems stem from developers viewing them as if they are. Macros' "return values" are code snippets, not values. E.g.
#define max(a, b) ((a) < (b) ? (b) : (a))
The problem here is not that 'max' has a problem with multiple evaluation; the problem is that typical developers view 'max' as returning either 'a' or 'b'. In other words, whether an abstraction is a macro is an important documentation point, and the documentation of an abstraction, regardless of what it is (macro, function, class, etc.), dictates the entirety of how it is to be used.
Agreed. In the case of max, it's the syntactic similarity with a function call that lures people into making certain assumptions. Same goes about non-macro entities such as auto_ptr - "a = b;" looks like something that ain't.
By the way, it was FOREACH that inspired me to figure out a simple solution to max, which then Eric improved. It's basically like this:
#define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b)))
which presto, nice-o passes the appropriate types to a template max_fn function that now can be smart about returning lvalues and so on.
You have stumped me here. Why does one not do: #define max(a, b) (max_fn((a),(b)) instead ?

Edward Diener wrote:
Andrei Alexandrescu (See Website For Email) wrote:
By the way, it was FOREACH that inspired me to figure out a simple solution to max, which then Eric improved. It's basically like this:
#define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b)))
which presto, nice-o passes the appropriate types to a template max_fn function that now can be smart about returning lvalues and so on.
You have stumped me here. Why does one not do:
#define max(a, b) (max_fn((a),(b))
instead ?
I thought nobody's gonna ask :o). The problem on the former code is that it puts a lot of aggravation on max_fn as far as deducing the proper type returned; see http://moderncppdesign.com/publications/cuj-04-2001.html for a discussion on why. On the contrary, the trick (used also, and inspired from, FOREACH): #define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b))) lets the ?:'s rules figure out that type; now max_fn has two arguments of the same type, and can much more easily figure out what to return (only needs to deal with const and rvalues, stuff that was piece of cake for Eric to figure out). Andrei

Andrei Alexandrescu (See Website For Email) wrote:
Edward Diener wrote:
Andrei Alexandrescu (See Website For Email) wrote:
By the way, it was FOREACH that inspired me to figure out a simple solution to max, which then Eric improved. It's basically like this:
#define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b)))
which presto, nice-o passes the appropriate types to a template max_fn function that now can be smart about returning lvalues and so on.
You have stumped me here. Why does one not do:
#define max(a, b) (max_fn((a),(b))
instead ?
I thought nobody's gonna ask :o).
The problem on the former code is that it puts a lot of aggravation on max_fn as far as deducing the proper type returned; see http://moderncppdesign.com/publications/cuj-04-2001.html for a discussion on why.
On the contrary, the trick (used also, and inspired from, FOREACH):
#define max(a, b) (max_fn(true ? (a) : (b), false ? (a) : (b)))
lets the ?:'s rules figure out that type; now max_fn has two arguments of the same type, and can much more easily figure out what to return (only needs to deal with const and rvalues, stuff that was piece of cake for Eric to figure out).
I just re-read the conditional operator specs in 5.16 of the standard and did not realize how much was there. Thanks for alerting me to this area of the language.
participants (7)
-
Andrei Alexandrescu (See Website For Email)
-
brangdon@cix.compulink.co.uk
-
Edward Diener
-
Eric Niebler
-
Gennadiy Rozental
-
Joel
-
Paul Mensonides