易码技术论坛

 找回密码
 加入易码
搜索
查看: 96633|回复: 6

[归档] 【转载几篇技术文档】其一:boost::lamda

[复制链接]
发表于 2006-7-19 15:23:00 | 显示全部楼层
c++创始人对c++的认识,对编程的认识

连接:http://www.artima.com/intv/abstreffi3.html

A Conversation with Bjarne Stroustrup, Part III
by Bill Venners
February 16, 2004


Summary
Bjarne Stroustrup talks with Bill Venners about raising the level of abstraction, why programming is understanding, how "oops happens," and the difference between premature and prudent optimization.

Bjarne Stroustrup is the designer and original implementer of C++. He is the author of numerous papers and several books, including The C++ Programming Language (Addison-Wesley, 1985-2000) and The Design and Evolution of C++ (Addison-Wesley, 1994). He took an active role in the creation of the ANSI/ISO standard for C++ and continues to work on the maintenance and revision of that standard. He is currently the College of Engineering Chair in Computer Science Professor at Texas A&M University.

On September 22, 2003, Bill Venners met with Bjarne Stroustrup at the JAOO conference in Aarhus, Denmark. In this interview, which is being published in multiple installments on Artima.com, Stroustrup gives insights into C++ best practice.
In Part I: The C++ Style Sweet Spot, Stroustrup describes how C++ programmers can reconsider their style of C++ use to gain maximum benefit from the language.
In Part II: Modern C++ Style, Stroustrup discusses using multiple inheritance and pure abstract classes, multi-paradigm programming, and the technique of resource acquisition is initialization.
In this third installment, Stroustrup discusses raising the level of abstraction, why programming is understanding, how "oops happens," and the difference between premature and prudent optimization. Raising the Level of Abstraction

Bill Venners: I originally learned C++ from Borland's "World of C++" video. At the beginning of that video, you have a brief cameo appearance in which you state that what you were trying to do in C++ is raise the level of abstraction for programming.

Bjarne Stroustrup: That's right.

Bill Venners: What does raising the level of abstraction mean, and why is a high level of abstraction good?

Bjarne Stroustrup: A high level of abstraction is good, not just in C++, but in general. We want to deal with problems at the level we are thinking about those problems. When we do that, we have no gap between the way we understand problems and the way we implement their solutions. We can understand the next guy's code. We don't have to be the compiler.

Abstraction is a mechanism by which we understand things. Expressing a solution in terms of math, for instance, means we really did understand the problem. We didn't just hack a bunch of loops to try out special cases. There is always the temptation to provide just the solution to a particular problem. However, unless we try to generalize and see the problem as an example of a general class of problems, we may miss important parts of the solution to our particular problems and fail to find concepts and general solutions that could help us in the future. If somebody has a theory, such as a theory for matrix manipulation, you can just work at the level of those concepts and your code will become shorter, clearer, and more likely to be correct. There's less code to write, and it's easier to maintain.

I believe raising the level of abstraction is fundamental in all practical intellectual endeavors. I don't consider that a controversial statement, but people sometimes consider it controversial because they think code at a higher level abstraction is necessarily less efficient. For example, I got an email two days ago from somebody who had heard me give a talk in which I had been arguing for using a matrix library with proper linear algebra support. He said, "How much does using the matrix library cost more than using arrays directly, because I'm not sure I can afford it." To his great surprise, my answer was, "If you want the efficiency I pointed to, you cannot use the arrays directly."

The only code faster than the fastest code is no code. By abstracting to matrix manipulation operations, you give the compiler enough type information to enable it to eliminate many operations. If you were writing the code at the level of arrays, you would not eliminate those operations unless you were smarter than just about everybody. So you'd not only have to write ten times as much code if you used arrays instead of the matrix library, but you'd also have to accept a program that runs more slowly. By operating at the level where we can understand things, sometimes you can also operate at the level where we can analyze the code—we being compilers in the second case—and get better code.
My two favorite examples of this phenomenon are matrix times vector operations, where C++ on a good day can beat Fortran, and simple sorts, where C++ on a good day can beat C. The reason in both cases is you've expressed the program so directly, so cleanly, that the type system can help in generating better code—and you can't do that unless you have a suitable level of abstraction. You get this beautiful case where your code gets clearer, shorter, and faster. It doesn't happen all the time, of course, but it is so beautiful when it does.



Programming is Understanding

Bill Venners: In the static versus dynamic typing debate, the proponents of strong typing often claim that although a dynamically typed language can help you whip up a prototype very quickly, to build a robust system you need a statically typed language. By contrast, the main message about static typing that I've gotten from you in your talks and writings has been that static typing can help an optimizer work more effectively. In your view, what are the benefits of static typing, both in C++ and in general?

Bjarne Stroustrup: There are a couple of benefits. First, I think you can understand things better in a statically typed program. If we can say there are certain operations you can do on an integer, and this is an integer, then we can know exactly what's going on.

Bill Venners: When you say we know what's going on, do you mean programmers or compilers?

Bjarne Stroustrup: Programmers. I do tend to anthropromorphize, though.

Bill Venners: Anthropromorphize programmers?

Bjarne Stroustrup: Anthropomorphize compilers. I tend to do that partly because it's tempting, and partly because I've written compilers. So as programmers, I feel we can better understand what goes on with a statically typed language.

In a dynamically typed language, you do an operation and basically hope the object is of the type where the operation makes some sense, otherwise you have to deal with the problem at runtime. Now, that may be a very good way to find out if your program works if you are sitting at a terminal debugging your code. There are nice quick response times, and if you do an operation that doesn't work, you find yourself in the debugger. That's fine. If you can find all the bugs, that's fine when it's just the programmer working—but for a lot of real programs, you can't find all the bugs that way. If bugs show up when no programmer is present, then you have a problem. I've done a lot of work with programs that should run in places like telephone switches. In such environments, it's very important that unexpected things don't happen. The same is true in most embedded systems. In these environments, there's nobody who can understand what to do if a bug sends them into a debugger.

With static typing, I find it easier to write the code. I find it easier to understand the code. I find it easier to understand other people's code, because the things they tried to say are expressed in something with a well-defined semantics in the language. For example, if I specify my function takes an argument of type Temperature_reading then a user does not have to look at my code to determine what kind of object I need, looking at the interface will do. I don't need to check if the user gave me the wrong kind of object, because the compiler will reject any argument that is not a Temperature_reading. I can directly use my argument as a Temperature_reading without applying any type of cast. I also find that developing those statically typed interfaces is a good exercise. If forces me to think about what is essential, rather than just letting anything remotely plausible through as arguments and return values, hoping that the caller and the callee will agree and that both will write the necessary runtime checks.

To quote Kristen Nygaard, programming is understanding. The meaning is: if you don't understand something, you can't code it, and you gain understading trying to code it. That's the foreword vignette in my third edition of The C++ Programming Language. That is pretty fundamental, and I think it's much easier to read a piece of code where you know you have a vector of integers rather than a pointer to an object. Sure, you can ask whether the object is a vector, and if so you can ask if it holds integers. Or perhaps it holds some integers, some strings, and some shapes. If you want such containers you can build them, but I think you should prefer homogeneous vectors, vectors that hold a specific type as opposed to a generic collection of generic objects. Why? It's really a variant of the argument for preferring statically checked interfaces. If I have a vector<Apple>, then I know that its elements are Apples. I don't have to cast an Object to an Apple to use it, and I don't have to fear that you have treated my vector as a vector<Fruit> and snuck a Pear into it, or treated it as an vector<Object> and stuck an HydraulicPumpInterface in there. I thought that was pretty well understood by now. Even Java and C# are about to provide generic mechanisms to support that.

On the other hand, you can't build a system that is completely statically typed, because you would have to deploy the whole system compiled as one unit that never changes. The benefits of more dynamic techniques like virtual functions are that you can connect to something you don't quite know enough about to do complete static type checking. Then, you can check what interfaces it has using whatever initial interfaces you know. You can ask an object a few questions and then start using it based on the answers. The question is along the lines of, "Are you something that obeys the Shape interface?" If you get yes, you start applying Shape operations to it. If you get no, you say, "Oops," and you deal with it. The C++ mechanism for that is dynamic_cast. This "questioning" using dynamic_cast contrasts with dynamically typed languages, where you tend to just start applying the operations. If it doesn't work, you say, "Oops." Often, that oops happens in the middle of a computation as opposed to the point when the object becomes known to you. It's harder to deal with a later oops.

Also, the benefits to the compiler in terms of optimization can be huge. The difference between dynamically and a statically typed and resolved operation can easily be times 50. When I talk about efficiencies, I like to talk about factors, because that's where you can really see a difference.

Bill Venners: Factors?

Bjarne Stroustrup: When you get to percents, 10%, 50%, and such, you start arguing whether efficiency matters, whether next year's machine will be the right solution rather than optimization. But in terms of dynamic versus static, we're talking factors: times 3, times 5, times 10, times 50. I think a fair bit about real-time problems that have to be done on big computers, where a factor of 10 or even a factor of 2 times is the difference between success and failure.

Bill Venners: You're not just talking about dynamic versus static method invocation. You're talking about optimization, right? The optimizer has more information and can do a better job.

Bjarne Stroustrup: Yes.

Bill Venners: How does that work? How does an optimizer use type information to do a better job of optimizing?
Bjarne Stroustrup: Let's take a very simple case. C++ has both statically and dynamically bound member functions. If you do a virtual function call, it's an indirect function call. If it's statically bound, it's a perfectly ordinary function call. An indirect function call is probably 25% more expensive these days. That's not such a big deal. But if it's a really small function that does something like a less-than operation on an integer, the relative cost of a function call is huge, because there's more code to be executed. You have to do the function preamble. You have to do the operation. You have to do the postamble, if there is such a thing. In the process of doing all that, you have to get more instructions loaded into the machine. You break the pipelines, especially if it's an indirect function call. So you get one of these 10 to 30 factors for how to do a less-than. If such a difference occurs in a critical inner loop, the difference becomes significant. That was how the C++ sort beat the C sort. The C sort passed a function to be called indirectly. The C++ version passed a function object, where you had a statically bound inline function that degenerated into a less than.
 楼主| 发表于 2006-7-19 15:24:00 | 显示全部楼层
Premature or Prudent Optimization


Bill Venners: C++ culture is concerned with efficiency. Is there a lot of premature optimization going on? And how do we know the difference between early optimization that's premature versus early optimization that's prudent?


Bjarne Stroustrup: Some parts of the C++ community are concerned with efficiency. Some of them, I think, are concerned for good reasons, others just because they don't know any better. They have a fear of inefficiency that's not quite appropriate. But certainly there's an efficiency concern, and I think there are two ways of looking at it. The way I would look at efficiency is this: I would like to know that my abstractions could map in a reasonable way to the machine, and I would like to have abstractions that I can understand.


If I want to do linear algebra, I want a matrix class. If I want to do graphics, I want a graphics class. If I want to do string manipulation, I want a string class. The first thing I do is raise the level of abstraction to a suitable level. I'm using these fairly simple examples, because they're the most common and the easiest to talk about. The next thing I look out for is not to have an N2 or N3 algorithm where I don't need it. I don't go to the web for information if I have the information locally. I don't go to the disk if I have a cached version in memory. I've seen people using modeling tools that ended up writing to the disk twice to write two fields into a record. Avoid such algorithms. I think this is prudent up front design-level optimization, which is the kind of thing you should be concerned with.


Now, once you have a reasonably modeled world, with reasonably high level of abstraction, you start optimizing, and that sort of late optimization is reasonable. What I don't like is when people, who out of fear of high level features and fear of abstraction, start using a very restricted subset of the language or avoid good libraries in favor of their own hand-crafted code. They deal with bytes where they could just as well deal with objects. They deal with arrays because they fear that a vector or a map class will be too expensive for them. Then, they end up writing more code, code that can't be understood later. That's a problem, because in any big system you'll have to analyze it later and figure out where you got it wrong.

You also try to have higher abstractions so you can measure something concrete. If you use a map, you may find that it's too expensive. That's quite possible. If you have a map with a million elements, there's a good chance it could be slow. It's a red black tree. In many cases, you can replace a map with a hashtable if you need to optimize. If you only have 100 elements, it won't make any difference. But with a million elements, it can make a big difference.
Now, if you've hacked at all at the lowest level, even once, you won't really know what you have. Maybe you knew your data structure was a map, but more likely it was an ad hoc map-like data structure. Once you realize that the ad hoc data structure didn't behave correctly, how do you know which one you can replace it with? You're working at such a low level that it's hard to get ideas. And then finally, if you've written an ad hoc data structure, you may have operations scattered all over your program. That's not uncommon with a random data structure. There's not a fixed set of operations you use to manipulate it, sometimes data is access directly from user code "for efficiency". In that case, your profiler won't show you where the bottleneck is, because you have scattered the code across the program. Conceptually the bottleneck belongs to something, but you didn't have the concept, or you didn't represent the concept directly. Your tools therefore cannot show you that this concept is what caused your problem. If something isn't in the code directly, no tool can tell you about that something by its proper name.

 楼主| 发表于 2006-7-19 15:25:00 | 显示全部楼层
神来之笔啊,还没看完,可以去买他的《Modern c++ design》,有中译本

连接:http://www.ddj.com/dept/cpp/184403813

Generic Programming:Typelists and Applications

Andrei Alexandrescu

This month's installment of "Generic reintroduces typelists, discusses alternatives for implementing typelist creation, and presents a concrete application of typelists in a real-world problem: visiting a hierarchy that wasn't designed for visitation.




It's not exactly known how it started.

I only can tell my side of the story: I first saw some weird recursive templates in a post on the Usenet newsgroup comp.lang.c++.moderated (highly recommended, by the way). Later, while in desperate need to automate an Abstract Factory implementation, a certain structure started to occupy my mind.

Other people have independently gone through a similar process, because typelists are all over the place, in various flavors. Czarnecki and Eisenecker introduce in [1] a typelist that actually holds constant integral values. The MPL library [2] defines typelists as well, together with a host of algorithms that operate on them. Modern C++ Design also describes a simple typelist implementation and applies it in no less than five generic components (Functor, Visitor, AbstractFactory, StaticDispatcher, and Tuple), not to mention two automatic hierarchy generators (GenScatterHierarchy and GenLinearHierarchy).

By now the usefulness and applicability of typelists is proven, and knowing about them would certainly improve your arsenal of techniques and your understanding of existing and future C++ libraries. At least that smart intern won't catch you in off guard during a water cooler chat. (Provided that your employer would hire interns these days — but well, that's another story.)

This article reintroduces typelists, together with an interesting application for solving a real-world problem. If you can't tell a typelist from a Christmas wish list, you're in the right place. If you absorb like a sponge everything that's going on in C++, you might still find some interesting stuff, so, read on.



So What Is a Typelist?


It's got to be one of those weird template beasts, right? Actually, typelists are so simple, and so devoid of any value (in the most literal sense), that it's actually quite amazing the C++ community didn't put them to work earlier.


template <class H, class T>
struct typelist
{
    typedef H head;
    typedef T tail;
};
That's really all there is to it! Nothing up my sleeves, really. Nevertheless, with this simple cell you can build typelists of any length, using the tail composition pioneered by LISP a long time ag

typedef typelist<float, typelist<double, long double> >
    floating_point_types;
You can do this because a typelist is a type, and as such it can participate as one of the types in a larger typelist. By convention, a typelist can appear in another typelist only in the tail position, not in the head; this makes typelist processing simpler, without reducing flexibility.

We're almost there; we only need to define an ancillary type that marks the end of a typelist (analogous to LISP's nil). Let's call it null_typelist:

class null_typelist {};
So the correct version of the typelist that holds all floating points is:


typedef typelist<float, typelist<double,
    typelist<long double, null_typelist> > >
    floating_point_types;
Linearizing Typelist Creation: Three SchoolsIt doesn't take a vulture's eye to see that creating typelists becomes extremely awkward beyond only a few elements. All those nested template instantiations and the closing >s that need to have a space between them, to avoid confusion with the >> operator — ah, the joys of C++ parsing — are quite unwieldy.

There are several schools of thought in tackling this problem. Loki [3] relies on macros, which are defined as follows:

#define TYPELIST_1(T1) typelist<T1, null_typelist>
#define TYPELIST_2(T1, T2) typelist<T1, TYPELIST_1(T2) >
#define TYPELIST_3(T1, T2, T3) typelist<T1, TYPELIST_2(T2, T3) >
...
#define TYPELIST_50...
As you see, each macro relies on the previous one. Loki defines such "constructors" for typelists with up to 50 elements. Beyond that limit, you can either define new macros or concatenate typelists programatically.

I should have known better than to use macros. Text transformation is very powerful, but C++'s macro preprocessor is so primitive, that, almost invariably, when you thought you did something actually interesting with it, it comes from behind to bite you.

In this case, the problem is that you cannot use the macros with simple templates, for example:

typedef TYPELIST_2(vector<int, allocator>,
    vector<int, my_allocator>)
    storage_choices;
The preprocessor can handle parentheses, but not the angle brackets < and >. So, the text inside TYPELIST_2 looks like four parameters to the preprocessor — namely (1) vector<int, (2) allocator>, (3) vector<int, and (4) my_allocator> — not two as intended. Worse, some preprocessors won't even issue an error — they will just silently ignore arguments (3) and (4) and generate a nonsensical expansion that's bound to cause you a headache.

A better solution is to use templates for generating typelists. The idea is as follows:

template <class T1>
struct cons<T1, null_typelist, null_typelist,
    null_typelist>
{
    typedef typelist<T1, null_typelist> type;
};

template <class T1, class T2>
struct cons<T1, T2, null_typelist, null_typelist>
{
    typedef typelist<T1, typelist<T2,
        null_typelist> > type;
};

template <class T1, class T2, class T3>
struct cons<T1, T2, T3, null_typelist>
{
    typedef typelist<T1, typelist<T2, typelist<T3,
        null_typelist> > > type;
};

template <class T1, class T2, class T3, class T4>
struct cons
{
    typedef typelist<T1, typelist<T2, typelist<T3,
        typelist<T4, null_typelist> > > > type;
};
The example above works for up to four types. You use cons like this:

typedef cons<float, double, long double>::type
    floating_point_types;
The cons template relies on partial specialization. When you instantiate cons<float, double, long double>, first the compiler fills in the fourth argument of cons with the default, so it's really about cons< float, double, long double, null_typelist>. Then, the compiler matches this instantiation against the four defined specializations.

The first specialization is not a match because it requires T2 to be null_typelist, while the concrete T2 is double. Similarly, the second specialization fails due to lack of agreement on T3. The third specialization is a valid candidate because it accepts arbitrary types for T1, T2, T3. All bets are not off yet; the compiler also checks the fourth specialization, which matches as well! It accepts arbitrary types for T1, T2, T3, and T4, so it doesn't have any trouble if T4 is null_typelist. But before you can say "compile-time error," a discriminator kicks in: the third instantiation is selected because it is more specialized than the fourth.

The algorithm for deciding "more specialized"-ness is quite complex, but at an intuitive level, a partial specialization of a template is more specialized than another if it is less general (has fewer degrees of freedom). When matching a concrete argument list against several partially specialized versions of a template, the winner is the most specialized version that can still accommodate the arguments. In our example, the third partial specialization of cons wins because it does match the instantiation cons<float, double, long double, null_typelist> and is more specialized than the fourth specialization. That is because the fourth specialization accepts any type for T4, while the third accepts only null_typelist for T4, thus being more specific.

You can extend cons to work with any number of types, but alas, you cannot extend that upper bound unless you do surgery on cons, or, again, if you rely on some preprocessor tricks.

The third approach uses function signatures. Consider the following:

template <class Fun> struct cons;

template <class T1> struct cons<void (*)(T1)>
{
    typedef typelist<T1, null_typelist> type;
};

template <class T1, class T2>
struct cons<void (*)(T1, T2)>
{
    typedef typelist<T1, typelist<T2,
        null_typelist> > type;
};

template <class T1, class T2, class T3>
struct cons< void (*)(T1, T2, T3)>
{
    typedef typelist<T1, typelist<T2,
        typelist<T3, null_typelist> > > type;
};

template <class T1, class T2, class T3, class T4>
struct cons<void (*)(T1, T2, T3, T4)>
{
    typedef typelist<T1, typelist<T2, typelist<T3,
        typelist<T4, null_typelist> > > > type;
};
Now if you define a macro like this:

#define TYPELIST(a) cons< void (*)a >::type
then you can write this:

typedef TYPELIST((float, double, long double))
    floating_point_types;
Not bad at all ... if you manage to convince people of the beauty of the two sets of parentheses.

Ok, I know I owe some explanations, so here they are. First thing, the void (*)(T1) is a C++ type (no typo, no emoticon in there!): a pointer to function that takes one argument of type T1 and returns void. The type is more familiar in the form void (*Fun)(T1), a construct that actually contains a name in it.

The cons class template is specialized on pointers to functions returning void and taking one, two, three, and four arguments of any type. Then, the macro TYPELIST expands the construct:

TYPELIST((float, double, long double))
int

cons< void (*) (float, double, long double) >::type
which in turn is exactly:

typelist<float, typelist<double,
    typelist<long double, null_typelist> > >
As a mini-conclusion to this section, no solution above is perfect; until some language support for variable template arguments is there, you will have to pay a mixed toll in cleanliness, convenience, extensibility, and ease of use.

For the remainder of the article, let's use the second solution — the one with cons<...>::type.

It is time now to put typelists to work. A previous article [4] shows, for example, how to compute the length of a typelist. In the following, we'll see a new application of typelists in real-world problems.
 楼主| 发表于 2006-7-19 15:25:00 | 显示全部楼层
Ad-Hoc VisitationThe Visitor design pattern offers you a clean solution to hairy problems, but it has drawbacks that limit its applicability. In essence, Visitor offers you a method for safely adding operations to a class hierarchy without touching the hierarchy itself, at the cost of having to modify all those operations when the hierarchy changes. For the full story, refer to the legendary Design Patterns book [5] or to the C++-centered description in Modern C++ Design [3].

One less mentioned problem with visitation is that you must implement support for the Visitor pattern in the hierarchy, in the form of an Accept member function; in other words, if you don't have write access to a hierarchy that hasn't been designed to support Visitor, you cannot, well, visit it.

If support for visitation is missing, and if you want to add a new operation to a class hierarchy without touching it, you are doomed to rely on the dreaded switch-on-type approach. The common real-world case is when you use some base classes from an ancient class library (deployed by You-Know-Who) and add your own derived classes.

Consider, for example, a hierarchy rooted in DocumentItem featuring the concrete types TextArea, VectorGraphics, and Bitmap, all directly derived from DocumentItem. To add SomeOperation, you can do the following:

void SomeOperation(DocumentItem* p)
{
    if (TextArea* pTextArea = dynamic_cast<TextArea*>(p))
    {
        ... operate on a TextArea object ...
    }
    else if (VectorGraphics* pVectorGraphics =
        dynamic_cast<VectorGraphics*>(p))
    {
        ... operate on a VectorGraphics object ...
    }
    else if (Bitmap* pBitmap = dynamic_cast<Bitmap*>(p))
    {
        ... operate on a Bitmap object ...
    }
    else
    {
        throw "Unknown type passed";
    }
}
In addition to being thoroughly ugly, the code above has the conceptual problem of being unable to catch at compile time the "forgot to handle this type" error. If our hypothetical hierarchy gets extended with a new type PieChart, or if the implementer of SomeOperation above forgets to treat an already existing type, the only solution is to resort to throwing an exception at run time.

Maybe one such blob is not really bad, but three or more certainly are, slowly transmogrifying your application into a Dr. Moreau's island [6].

A partial approach to this problem is to somehow automate and factor out the code that computes the casts from the code that does something. This way, there will be a unique point of maintenance when you change the hierarchy. Here's where typelists can be of help.

Consider the code below:

template <class tlist> class AdHocVisitor;

template <class H, class T>
class AdHocVisitor< typelist<H, T> >
    : public AdHocVisitor<T>
{
public:
    using AdHocVisitor<T>::Visit;
    virtual void Visit(H*) = 0;
    template <class SomeClass>
    void StartVisit(SomeClass* p)
    {
        if (H* pFound = dynamic_cast<H*>(p))
        {
            Visit(pFound);
        }
        else
        {
            AdHocVisitor<T>::StartVisit(p);
        }
    }
};

template <class H>
class AdHocVisitor< typelist<H, null_typelist> >
{
public:
    virtual void Visit(H*) = 0;
    template <class SomeClass>
    void StartVisit(SomeClass* p)
    {
        if (H* pFound = dynamic_cast<H*>(p))
        {
            Visit(pFound);
        }
        else
        {
            throw "Unknown type passed";
        }
    }
};AdHocVisitor accepts a typelist as its argument and uses a technique similar to the one used by cons. Namely, AdHocVisitor has two specializations: one that handles the head of the typelist and recurses on the tail, and one that stops the recursion.

The first specialization defines a pure virtual function Visit(H*), where H is the head of the typelist. Also, AdHocVisitor inherits itself, this time instantiated with the tail of the typelist. Essentially, AdHocVisitor<tl> defines a pure virtual function Visit for each type in the typelist tl.

This is a good start, because it's what the Visitor pattern prescribes. But we also need to do the actual dispatch. This is the job of StartVisit, which has quite an interesting implementation.

Let's start with the implementation of StartVisit in the first specialization of AdHocVisitor. It tries a dynamic_cast on the passed-in pointer (which, quite remarkably, can be of any type). If the cast succeeds, StartVisit materializes the visit by calling the pure virtual function Visit and passing it the result of the cast. If the cast does not succeed, StartVisit invokes AdHocVisitor<T>::StartVisit. AdHocVisitor<T> being the base class of AdHocVisitor< typelist<H, T> >, the call is nothing else but a straight invocation of the base class member function. That function will continue to perform the dynamic_cast in search for the right type.

The recursion must end at some point, and this is the role of the second specialization of AdHocVisitor. That specialization handles a typelist with only one element (the other one being null_typelist). The difference is that, in case the dynamic_cast fails, StartVisit throws an exception. As you'll soon see, that's not as bad as it looks.

Now let's see how to use the newly defined AdHocVisitor. Consider the definition above in place of a simple main function:

int main()
{
    typedef cons<TextArea, VectorGraphics,
        Bitmap>::type MyHierarchy;
    DocElement *p = new Bitmap;
    struct ConcreteVisitor : AdHocVisitor<MyHierarchy>
    {
        void Visit(TextArea* p)
        {
            ...
        }
        void Visit(VectorGraphics* p)
        {
            ...
        }
        void Visit(Bitmap* p)
        {
            ...
        }
    };
    ConcreteVisitor visitor;
    visitor.StartVisit(p);
}Let's see how it all works. The first line defines MyHierarchy as a typelist containing TextArea, VectorGraphics, and Bitmap. Note that the root class, DocElement, is not part of the typelist.

The ConcreteVisitor class is defined right inside the main function — quite in spirit of the "ad hoc" name of the implementation. As you'd expect, ConcreteVisitor inherits AdHocVisitor<MyHierarchy> and overrides all three pure virtual functions that AdHocVisitor<MyHierarchy> declares.

When the main function calls StartVisit against p, the mechanism described above enters into action and smoothly dispatches execution to the appropriate Visit overload.


Analysis of Ad Hoc VisitationThis is one of those cases where analyzing the outcome of some code is as satisfying as hacking the code together.


EfficiencyWell, compared to the clumsy if/else solution presented above, this solution isn't as efficient. We still do a dynamic_cast, and we do an extra virtual function call (Visit).

The virtual call can be eliminated quite easily by taking the following steps:


Have AdHocVisitor take a second template argument Effector, and derive from it.
Instead of defining the pure virtual functions Visit, just have AdHocVisitor assume those functions are defined by Effector and use them.
(Optional) Inside each successful dynamic_cast branch, ensure at compile time that Effector::Visit has the appropriate signature. The compile-time test can be done quite easily by taking the address of Effector::Visit.
Take ConcreteVisitor out of main. There's nothing conceptually wrong with ConcreteVisitor being inside a function, but, alas, C++ does not allow local types to be passed as template arguments.
Use the AdHocVisitor template, passing it a typelist and your ConcreteVisitor class. You put the whole engine in motion by invoking StartVisitation.Why didn't the article discuss this optimized solution from the very beginning (rather than leave it in the dreaded form of an exercise for the reader)? The optimized solution needs pounding out of some nuts and bolts, and this article's focus is applying typelists.


CouplingBoth solutions need knowledge about the hierarchy. However, it is important to note that the brute force solution asks you to spread that hierarchy knowledge throughout the code; the typelist-based solution allows you to concentrate that knowledge in a unique place: you just provide a typedef, and you change it only when you change the hierarchy.


MaintainabilityDon't forget that we're already talking about a special situation: you cannot change the class hierarchy to which you want to add operations. That does impose some disadvantages upon us from the outset.

This being said, the typelist-based solution brings a very important maintainability boon. Consider you forget to handle the Bitmap type in your ConcreteVisitor class:

    struct ConcreteVisitor : AdHocVisitor<MyHierarchy>
    {
        void Visit(TextArea* p)
        {
            ...
        }
        void Visit(VectorGraphics* p)
        {
            ...
        }
    };


Then the compiler will not allow you to instantiate ConcreteVisitor, because AdHocVisitor<MyHierarchy> defines a virtual pure function void Visit(Bitmap*), and, as you well know, pure functions are required to be overridden!

In other words, you ensure that, once you have defined the MyHierarchy typelist correctly, all handling omissions in all code will be flagged at compile time. This unique point of maintenance design is very appealing. (Unfortunately, however, the solution does not handle the case in which ConcreteVisitor has a spurious handler, say void Visit(IrrelevantType*)).



Conclusion


This article presented a typelist implementation in C++, together with a complete use example. Typelists are powerful and important, mostly because they enable completely new idioms, thus bringing better solutions to complex problems. Most of all, typelists encourage reuse by allowing you to factor out tediously repetitive code.



Bibliography and References


[1] K. Czarnecki and U. Eisenecker. Generative Programming: Methods, Tools, and Applications (Addison-Wesley Longman, 2000).

[2] See <www.boost.org>.

[3] A. Alexandrescu. Modern C++ Design (Addison-Wesley Longman, 2001).

[4] J. Vlissides and A. Alexandrescu. "To Code or Not to Code II," C++ Report, June 2000, <www.research.ibm.com/designpatterns/pubs/ph-jun00.pdf>.

[5] E. Gamma, et al. Design Patterns (Addison-Wesley Longman, 1995).

[6] H.G. Wells' novel The Island of Dr. Moreau (Dover, 1996) describes a mad doctor who transformed animals into humans on an isolated island. What he couldn't predict, however, was that his creatures were slowly returning back to their animal state, causing themselves and him much harm in the process.



Andrei Alexandrescu is a Ph.D. student at University of Washington in Seattle, and author of the acclaimed book Modern C++ Design. He may be contacted at www.moderncppdesign.com. Andrei is also one of the featured instructors of The C++ Seminar (www.gotw.ca/cpp_seminar)
 楼主| 发表于 2006-7-19 15:28:00 | 显示全部楼层
在《c++ template》上有相关内容,看看当年c++是怎么超过Fortron的吧

连接:http://osl.iu.edu/~tveldhui/papers/Expression-Templates/exprtmpl.htmlExpression TemplatesTodd Veldhuizen
Abstract: Expression Templates is a C++ technique for passing expressions as function arguments. The expression can be inlined into the function body, which results in faster and more convenient code than C-style callback functions. This technique can also be used to evaluate vector and matrix expressions in a single pass without temporaries. In preliminary benchmark results, one compiler evaluates vector expressions at 95-99.5% efficiency of hand- coded C using this technique (for long vectors). The speed is 2-15 times that of a conventional C++ vector class.



Introduction
Passing an expression to a function is a common occurrence. In C, expressions are usually passed using a pointer to a callback function containing the expression. For example, the standard C library routines qsort(), lsearch(), and bsearch() accept an argument int (*cmp)(void*, void*) which points to a user-defined function to compare two elements. Another common example is passing mathematical expressions to functions. The problem with callback functions is that repeated calls generate a lot of overhead, especially if the expression which the function evaluates is simple. Callback functions also slow pipelined machines, since the function calls force the processor to stall.


The technique of expression templates allows expressions to be passed to functions as an argument and inlined into the function body. Here are some examples:

          // Integrate a function from 0 to 10
          DoublePlaceholder x;
          double result = integrate(x/(1.0+x), 0.0, 10.0);
         
          // Count the elements between 0 and 100 in a
          collection of int
          List<int> myCollection;
          IntPlaceholder y;
          int n = myCollection.count(y >= 0 && y <= 100);
         
          // Fill a vector with a sampled sine function
          Vector<double> myVec(100);
          Vector<double>::placeholderType i;
          myVec.fill(sin(2*M_PI*i/100));  // eqn in terms of element #

In each of these examples, the compiler produces a function instance which contains the expression inline. The technique has a similar effect to Jensen's Device in ALGOL 60 [1]. Expression templates also solve an important problem in the design of vector and matrix class libraries. The technique allows developers to write code such as


          Vector y(100), a(100), b(100), c(100),
          d(100);
          y = (a+b)/(c-d);
         

and have the compiler generate code to compute the result in a single pass, without using temporary vectors.


So how does it work? The trick is that the expression is parsed at compile time, and stored as nested template arguments of an "expression type. Let's work through a simplified version of the first example, which passes a mathematical function to an integration routine. We'll invoke a function evaluate() which will evaluate f(x) at a range of points:           
          int main()
          {
              DExpr<DExprIdentity> x;         // Placeholder
              evaluate(x/(1.0+x), 0.0, 10.0);
              return 0;
          }

For this example, an expression is represented by an instance of the class DExpr[url] (short for double expression). This class is a wrapper which disguises more interesting expression types. DExpr[url] has a template parameter (A) representing the interesting expression, which can be of type DBinExprOp (a binary operation on two subexpressions), DExprIdentity (a placeholder for the variable x), or DExprLiteral (a constant or literal appearing in the expression). For example, the simple subexpression "1.0+x" would be represented by the expression type shown in Figure 1. These expression types are inferred by the compiler at compile time.



Figure 1. (A) Parse tree of "1.0+x", showing the similarity to the expression type generated by the compiler (B). The type DApAdd is an applicative template class which represents an addition operation. In (C), the type name is shown in a non-expanded view. For this implementation, type names are similar to a postfix traversal of the expression tree. To write a function which accepts an expression as an argument, we include a template parameter for the expression type. Here is the code for the evaluate() routine. The parenthesis operator of expr is overloaded to evaluate the expression:

template<class Expr>
void evaluate(DExpr<Expr> expr, double start, double end)
{
    const double step = 1.0;

    for (double i=start; i < end; i += step)
        cout << expr(i) << endl;
}


When the above example is compiled, an instance of evaluate() is generated which contains code equivalent to the line
              cout << i/(1.0+i) >> endl;

This happens because when the compiler encounters x/(1.0+x) in
              evaluate(x/(1.0+x), 0.0, 10.0);

it uses the return types of overloaded + and / operators to infer the expression type shown in Figure 2. Note that although the expression object is not instantiated until run time, the expression type is inferred at compile time. An instance of this expression type is passed to the evaluate() function as the first argument. When the fragment expr(i) is encountered, the compiler builds the expression inline, substituting i for the placeholder x. The mechanics of this are explained further on.



Figure 2. Template instance generated by x/(1.0+x): DExpr<DBinExprOp< DExpr <DExprIdentity>, DExpr<DBinExprOp<DExpr<DExprLiteral>, DExpr<DExprIdentity>, DApAdd>>, DApDivide>> As an example of how the return types of operators cause the compiler to infer expression types, here is an operator/() which produces a type representing the division of two subexpressions DExpr[url] and DExpr:

          template<class A, class B>
          DExpr<DBinExprOp<DExpr[url], DExpr, DApDivide> >
          operator/(const DExpr[url]& a, const DExpr& b)
          {
              typedef DBinExprOp<DExpr[url], DExpr,DApDivide> ExprT;
              return DExpr<ExprT>(ExprT(a,b));
          }

The return type contains a DBinExprOp, which represents a binary operation, with template parameters DExpr[url] and DExpr (the two subexpressions being divided), and DApDivide, which is an applicative template class encapsulating the division operation. The return type is a DBinExprOp<...> disguised by wrapping it with a DExpr<...> class. If we didn't disguise everything as DExpr<>, we would have to write eight different operators to handle the combinations of DBinExprOp<>, DExprIdentity, and DExprLiteral which can occur with operator/(). (More if we wanted unary operators!)


The typedef ExprT is the type DBinExprOp< ... , ... , DApDivide> which is to be wrapped by a DExpr<...>. Expression classes take constructor arguments of the embedded types; for example, DExpr[url] takes a const A& as a constructor argument. So DExpr<ExprT> takes a constructor argument of type const ExprT&. These arguments are needed so that instance data (such as literals which appear in the expression) are preserved.

The idea of applicative template classes has been borrowed from the Standard Template Library (STL) developed by Alexander Stepanov and Meng Lee, of Hewlett Packard Laboratories [2]. The STL has been accepted as part of the ISO/ANSI Standard C++ Library. In the STL, an applicative template class provides an inline operator() which applies an operation to its arguments and returns the result. For expression templates, the application function can (and should) be a static member function. Since operator() cannot be declared static, an apply() member function is used instead:

          // DApDivide -- divide two doubles
          class DApDivide {
          public:
              static inline double apply(double a, double b)
              { return a/b; }
          };

When a DBinExprOp's operator() is invoked, it uses the applicative template class to evaluate the expression inline:


          template<class A, class B, class Op>
          class DBinExprOp {
              A a_;
              B b_;
         
          public:
              DBinExprOp(const A& a, const B& b)
                : a_(a), b_(b)
              { }
         
              double operator()(double x) const
              { return Op::apply(a_(x), b_(x)); }
          };

It is also possible to incorporate functions such as exp() and log() into expressions, by defining appropriate functions and applicative templates. For example, an expression object representing a normal distribution can be easily constructed:


          double mean=5.0, sigma=2.0;   // mathematical
          constants
          DExpr<DExprIdentity> x;
          evaluate( 1.0/(sqrt(2*M_PI)*sigma) * exp(sqr(x-mean)/
              (-2*sigma*sigma)), 0.0, 10.0);

One of the neat things about expression templates is that mathematical constants are evaluated only once at run time, and stored as literals in the expression object. The evaluate() line above is equivalent t


          double __t1 = 1.0/(sqrt(2*M_PI)*sigma);
          double __t2 = (-2*sigma*sigma);
          evaluate( __t1 * exp(sqr(x-mean)/__t2), 0.0,
          10.0);

Another advantage is that it's very easy to pass parameters (such as the mean and standard deviation) -- they're stored as variables inside the expression object. With pointers-to- functions, these would have to be stored as globals (messy), or passed as arguments in each call to the function (expensive).



Figure 3 shows a benchmark of a simple integration function, comparing callback functions and expression templates to manually inlined code. Expression template versions ran at 95% the efficiency of hand-coded C. With larger expressions, the advantage over pointers-to-functions shrinks. This advantage is also diminished if the function using the expression (in this example, integrate()) does significant computations other than evaluating the expression.
It is possible to generalize the classes presented here to expressions involving arbitrary types (instead of just doubles). Expression templates can also be used in situations where multiple variables are needed -- such as filling out a matrix according to an equation. Since most compilers do not yet handle member function templates, a virtual function trick has to be used to pass expressions to member functions of a class. This technique is illustrated in the next example, which solves a significant outstanding problem in the design of matrix and vector class libraries.
 楼主| 发表于 2006-7-19 15:30:00 | 显示全部楼层
Optimizing Vector ExpressionsC++ class libraries are wonderful for scientific and engineering applications, since operator overloading makes it possible to write algebraic expressions containing vectors and matrices:

          DoubleVec y(1000), a(1000), b(1000), c(1000),
          d(1000);
          y = (a+b)/(c-d);
Until now, this level of abstraction has come at a high cost, since vector and matrix operators are usually implemented using temporary vectors [3]. Evaluating the above expression using a conventional class library generates code equivalent t

          DoubleVec __t1 = a+b;
          DoubleVec __t2 = c-d;
          DoubleVec __t3 = __t1/__t2;
          y = __t3;
Each line in the code above is evaluated with a loop. Thus, four loops are needed to evaluate the expression y=(a+b)/(c- d). The overhead of allocating storage for the temporary vectors (__t1, __t2, __t3) can also be significant, particularly for small vectors. What we would like to do is evaluate the expression in a single pass, by fusing the four loops into one:

          // double *yp, *ap, *bp, *cp, *dp all point into the vectors.
          // double *yend is the end of the y vector.
          do {
              *yp = (*ap + *bp)/(*cp - *dp);
              ++ap; ++bp; ++cp; ++dp;
          } while (++yp != yend);
         
By combining the ideas of expression templates and iterators, it is possible to generate this code automatically, by building an expression object which contains vector iterators rather than placeholders. Here is the declaration for a class DVec, which is a simple "vector of doubles". DVec declares a public type iterT, which is the iterator type used to traverse the vector (for this example, an iterator is a double*). DVec also declares Standard Template Library compliant begin() and end() methods to return iterators positioned at the beginning and end of the vector:

          class DVec {
         
          private:
              double* data_;
              int length_;
         
          public:
              typedef double* iterT;
         
              DVec(int n)
                  : length_(n)
              { data_ = new double[n]; }
         
              ~DVec()
              { delete [] data_; }
         
              iterT begin() const
              { return data_; }
         
              iterT end() const
              { return data_ + length_; }
         
              template<class A>
              DVec& operator=(DVExpr[url]);
          };
Expressions involving DVec objects are of type DVExpr[url]. An instance of DVExpr[url] contains iterators which are positioned in the vectors being combined. DVExpr[url] lets itself be treated as an iterator by providing two methods: double operator*() evaluates the expression using the current elements of all iterators, and operator++() increments all the iterators:

          template<class A>
          class DVExpr {
         
          private:
              A iter_;
         
          public:
              DVExpr(const A& a)
                  : iter_(a)
              { }
         
              double operator*() const
              { return *iter_; }
         
              void operator++()
              { ++iter_; }
          };
The constructor for DVExpr[url] requires a const A& argument, which contains all the vector iterators and other instance data (eg. constants) for the expression. This data is stored in the iter_ data member. When a DVExpr[url] is assigned to a Dvec, the Dvec:perator=() method uses the overloaded * and ++ operators of DVExpr[url] to store the result of the vector operation:

          template<class A>
          DVec& DVec:perator=(DVExpr[url] result)
          {
              // Get a beginning and end iterator
          for the vector
              iterT iter = begin(), endIter = end();
         
              // Store the result in the vector
              do {
                  *iter = *result;   // Inlined expression
                  ++result;
              } while (++iter != endIter);
         
              return *this;
          }
Binary operations on two subexpressions or iterators A and B are represented by a class DVBinExprOp<A,B,Op>. The operation itself (Op) is implemented by an applicative template class. The prefix ++ and unary * (dereferencing) operators of DVBinExprOp<A,B,Op> are overloaded to invoke the ++ and unary * operators of the A and B instances which it contains.

As before, operators build expression types using nested template arguments. Several versions of the each operator are required to handle the combinations in which DVec, DVExpr[url], and numeric constants can appear in expressions. For a simple example of how expressions are built, consider the code snippet

          DVec a(10), b(10);
          y = a+b;
         
Since both 'a' and 'b' are of type DVec, the appropriate operator+() is:

DVExpr<DVBinExprOp<DVec::iterT,DVec::iterT,DApAdd> >
operator+(const DVec& a, const DVec& b)
{
    typedef DVBinExprOp<DVec::iterT,DVec::iterT,DApAdd> ExprT;
    return DVExpr<ExprT>(ExprT(a.begin(),b.begin()));
}
All the operators which handle DVec types invoke DVec::begin() to get iterators. These iterators are bundled as part of the returned expression object. As before, it's possible to add to this scheme so that literals can be used in expressions, as well as unary operators (such as x = -y), and functions (sin, exp). It's also not difficult to write a templatized version of DVec and the DVExpr classes, so that vectors of arbitrary types can be used. By using another template technique called "traits", it's even possible to implement standard type promotions for vector algebra, so that adding a Vec<int> to a Vec<double> produces a Vec<double>, etc. Figure 4 shows the template instance inferred by the compiler for the expression y = (a+b)/(c-d). Although long and ugly type names result, the vector expression is evaluated entirely inline in a single pass by the assignResult() routine.


Figure 4. Type name for the expression (a+b)/(c-d): DVExpr<DVBinExprOp<DVExpr< DVBinExprOp<DVec::iterT, DVec::iterT, DApAdd>>, DVExpr<DVBinExprOp< DVec::iterT, DVec::iterT, DApSubtract>>, DApDivide>> Benchmark ResultsTo evaluate the efficiency of the expression templates approach to evaluating vector expressions, benchmark results were obtained comparing its performance to hand-crafted C code, and that of a conventional vector class. The performance was measured for the expression y=a+b+c.


Figure 5 shows the performance of the expression template technique, as compared to hand-coded C, using the Borland C++ V4.0 compiler and an 80486 processor. With a few adjustments of compiler options and tuning of the assignResult() routine, the same assembly code was generated by the expression template technique as hand coded C. The poorer performance for smaller vectors was due to the time spent in the expression object constructors. For longer vectors (eg., length 100) the benchmark results were 95-99.5% the speed of hand coded C.





Figure 6 compares the expression template technique to a conventional vector class which evaluates expressions using temporaries. For short vectors (up to a length of 20 or so), the overhead of setting up temporary vectors caused the conventional vector class to have very poor performance, and the expression template technique was 8-12 times faster. For long vectors, the performance of expression templates settles out to twice that of the conventional vector class. ConclusionsThe technique of expression templates is a powerful and convenient alternative to C style callback functions. It allows logical and algebraic expressions to be passed to functions as arguments, and inlined directly into the function body. Expression templates also solve the problem of evaluating vector and matrix expressions in a single pass without temporaries.


References[1] Rutihauser, H. Description of ALGOL 60, volume 1a of Handbook for Automatic Computation. Springer-Verlag, 1967.

[2] Stepanov, A. and Meng Lee, The Standard Template Library. ANSI X3J16-94-0095/ISO WG21-NO482.
[3] Budge, K. G. C++ Optimization and Excluding Middle- Level Code, Proceedings of the Second Annual Object-Oriented Numerics Conference, pp 107-121, Sunriver, Oregon, April 24- 27, 1994.
 楼主| 发表于 2006-7-18 15:30:19 | 显示全部楼层 |阅读模式
boost的lamda表达式的库,虽然有东施效颦之忧,不过能实现到这个程度,不得不钦佩作者的想象力和熟练的技巧

连接:http://www.boost.org/doc/html/lambda/using_library.html



Using the libraryIntroductory Examples
Parameter and return types of lambda functors
About actual arguments to lambda functors
Storing bound arguments in lambda functions
The purpose of this section is to introduce the basic functionality of the library. There are quite a lot of exceptions and special cases, but discussion of them is postponed until later sections.

Introductory Examples
In this section we give basic examples of using BLL lambda expressions in STL algorithm invocations. We start with some simple expressions and work up. First, we initialize the elements of a container, say, a list, to the value 1:
list<int> v(10);
for_each(v.begin(), v.end(), _1 = 1);
The expression _1 = 1 creates a lambda functor which assigns the value 1 to every element in v.[1]

Next, we create a container of pointers and make them point to the elements in the first container v:
vector<int*> vp(10);
transform(v.begin(), v.end(), vp.begin(), &_1);
The expression &_1 creates a function object for getting the address of each element in v. The addresses get assigned to the corresponding elements in vp.

The next code fragment changes the values in v. For each element, the function foo is called. The original value of the element is passed as an argument to foo. The result of foo is assigned back to the element:
int foo(int);
for_each(v.begin(), v.end(), _1 = bind(foo, _1));
The next step is to sort the elements of vp:
sort(vp.begin(), vp.end(), *_1 > *_2);
In this call to sort, we are sorting the elements by their contents in descending order.

Finally, the following for_each call outputs the sorted content of vp separated by line breaks:
for_each(vp.begin(), vp.end(), cout << *_1 << '\n');

Note that a normal (non-lambda) expression as subexpression of a lambda expression is evaluated immediately. This may cause surprises. For instance, if the previous example is rewritten as
for_each(vp.begin(), vp.end(), cout << '\n' << *_1);

the subexpression cout << '\n' is evaluated immediately and the effect is to output a single line break, followed by the elements of vp. The BLL provides functions constant and var to turn constants and, respectively, variables into lambda expressions, and can be used to prevent the immediate evaluation of subexpressions:
for_each(vp.begin(), vp.end(), cout << constant('\n') << *_1);

These functions are described more thoroughly in the section called “Delaying constants and variables”Parameter and return types of lambda functors
During the invocation of a lambda functor, the actual arguments are substituted for the placeholders. The placeholders do not dictate the type of these actual arguments. The basic rule is that a lambda function can be called with arguments of any types, as long as the lambda expression with substitutions performed is a valid C++ expression. As an example, the expression _1 + _2 creates a binary lambda functor. It can be called with two objects of any types A and B for which operator+(A,B) is defined (and for which BLL knows the return type of the operator, see below).

C++ lacks a mechanism to query a type of an expression. However, this precise mechanism is crucial for the implementation of C++ lambda expressions. Consequently, BLL includes a somewhat complex type deduction system which uses a set of traits classes for deducing the resulting type of lambda functions. It handles expressions where the operands are of built-in types and many of the expressions with operands of standard library types. Many of the user defined types are covered as well, particularly if the user defined operators obey normal conventions in defining the return types.

There are, however, cases when the return type cannot be deduced. For example, suppose you have defined:
C operator+(A, B);
The following lambda function invocation fails, since the return type cannot be deduced:
A a; B b; (_1 + _2)(a, b);
There are two alternative solutions to this. The first is to extend the BLL type deduction system to cover your own types (see the section called “Extending return type deduction system”). The second is to use a special lambda expression (ret) which defines the return type in place (see the section called “Overriding the deduced return type”):
A a; B b; ret<C>(_1 + _2)(a, b);
For bind expressions, the return type can be defined as a template argument of the bind function as well:
bind<int>(foo, _1, _2);About actual arguments to lambda functors
A general restriction for the actual arguments is that they cannot be non-const rvalues. For example:
int i = 1; int j = 2;
(_1 + _2)(i, j); // ok
(_1 + _2)(1, 2); // error (!)

This restriction is not as bad as it may look. Since the lambda functors are most often called inside STL-algorithms, the arguments originate from dereferencing iterators and the dereferencing operators seldom return rvalues. And for the cases where they do, there are workarounds discussed in the section called “Rvalues as actual arguments to lambda functors”.
Storing bound arguments in lambda functions
By default, temporary const copies of the bound arguments are stored in the lambda functor. This means that the value of a bound argument is fixed at the time of the creation of the lambda function and remains constant during the lifetime of the lambda function object. For example:
int i = 1;
(_1 = 2, _1 + i)(i);

The comma operator is overloaded to combine lambda expressions into a sequence; the resulting unary lambda functor first assigns 2 to its argument, then adds the value of i to it. The value of the expression in the last line is 3, not 4. In other words, the lambda expression that is created is lambda x.(x = 2, x + 1) rather than lambda x.(x = 2, x + i).

As said, this is the default behavior for which there are exceptions. The exact rules are as follows:


The programmer can control the storing mechanism with ref and cref wrappers [ref]. Wrapping an argument with ref, or cref, instructs the library to store the argument as a reference, or as a reference to const respectively. For example, if we rewrite the previous example and wrap the variable i with ref, we are creating the lambda expression lambda x.(x = 2, x + i) and the value of the expression in the last line will be 4:
i = 1;
(_1 = 2, _1 + ref(i))(i);

Note that ref and cref are different from var and constant. While the latter ones create lambda functors, the former do not. For example:
int i;
var(i) = 1; // ok
ref(i) = 1; // not ok, ref(i) is not a lambda functor

The functions ref and cref mostly exist for historical reasons, and ref can always be replaced with var, and cref with constant_ref. See the section called “Delaying constants and variables” for details. The ref and cref functions are general purpose utility functions in Boost, and hence defined directly in the boost namespace.


Array types cannot be copied, they are thus stored as const reference by default.


For some expressions it makes more sense to store the arguments as references. For example, the obvious intention of the lambda expression i += _1 is that calls to the lambda functor affect the value of the variable i, rather than some temporary copy of it. As another example, the streaming operators take their leftmost argument as non-const references. The exact rules are:


The left argument of compound assignment operators (+=, *=, etc.) are stored as references to non-const.


If the left argument of << or >> operator is derived from an instantiation of basic_ostream or respectively from basic_istream, the argument is stored as a reference to non-const. For all other types, the argument is stored as a copy.


In pointer arithmetic expressions, non-const array types are stored as non-const references. This is to prevent pointer arithmetic making non-const arrays const.
[1] Strictly taken, the C++ standard defines for_each as a non-modifying sequence operation, and the function object passed to for_each should not modify its argument. The requirements for the arguments of for_each are unnecessary strict, since as long as the iterators are mutable, for_each accepts a function object that can have side-effects on their argument. Nevertheless, it is straightforward to provide another function template with the functionality ofstd::for_each but more fine-grained requirements for its arguments.

[此贴子已经被作者于2006-7-19 15:33:15编辑过]

您需要登录后才可以回帖 登录 | 加入易码

本版积分规则

Archiver|手机版|小黑屋|EMAX Studio

GMT+8, 2024-4-19 09:57 , Processed in 0.017110 second(s), 19 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表