GotW #64

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ Style (Addison-Wesley, 2004) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard (1998) and its Technical Corrigendum (2003).

Standard Library Member Functions 
Difficulty: 5 / 10

Reuse is good, but can you always reuse the standard library with itself? Here is an example that might surprise you, where one feature of the standard library can be used portably with any of your code as much as you like, but it cannot be used portably with the standard library itself.

Problem

JG Question

1. What is std::mem_fun? When would you use it? Give an example.

Guru Question

2. Assuming a correct incantation in the indicated comment, is the following expression legal and portable C++? Why or why not?

std::mem_fun</*...*/>( &(std::vector<int>::clear) )

Solution

Fun With mem_fun

1. What is std::mem_fun? When would you use it? Give an example.

The standard mem_fun adapter lets you use member functions with standard library algorithms and other code that normally deals with free functions.

For example, given:

class Employee {
public:
  int DoStandardRaise() { /*...*/ }
  //...
};

int GiveStandardRaise( Employee& e )
{
  return e.DoStandardRaise();
}

vector<Employee> emps;

We might be used to writing code like the following:

std::for_each( emps.begin(), emps.end(), &GiveStandardRaise );

But suppose GiveStandardRaise() didn't exist, or for some other reason we needed to call the member function directly? Then we can write the following:

std::for_each( emps.begin(), emps.end(),
std::mem_fun_ref( &Employee::DoStandardRaise ) );

The "_ref" bit at the end of the name mem_fun_ref is a bit of an historical oddity. When writing code like the above, you should just remember to say "mem_fun_ref" if the container is a plain old container of objects, since for_each will be operating on references to those objects, and say "mem_fun" if it's a container of pointers to objects:

std::vector<Employee*> emp_ptrs; // <- note "*"
std::for_each( emp_ptrs.begin(), emp_ptrs.end(),
std::mem_fun( &Employee::DoStandardRaise ) );

You'll probably have noticed that, for clarity, I've been showing how to do this with functions that take no parameters. You can use the bind... helpers to deal with some functions that take an argument, and the principle is the same. Unfortunately you can't use this approach for functions that take two or more arguments. Still, it can be useful.

And that, a nutshell, is mem_fun. This brings us to the awkward part:

Use mem_fun With Anything, Except the Standard Library

2. Assuming a correct incantation in the indicated comment, is the following expression legal and portable C++? Why or why not?

std::mem_fun</*...*/>( &(std::vector<int>::clear) )

First, note that no "incantation" should be necessary. I deliberately wrote the question this way because as of this writing some popular compilers cannot correctly deduce the template parameters. For such compilers, depending on your implementation of the standard library, you would have to write something like:

std::mem_fun<void, std::vector<int, std::allocator<int> > >
  ( &(std::vector<int>::clear) );

Over time, this limitation will go away and compilers will be able to let you reliably omit the template parameters.

You might wonder why in the above I wrote "depending on your implementation of the standard library"... after all, the signature of std::vector<int>::clear() is that it takes no parameters and returns void, right? The standard tells us so, doesn't it?

Wrong (maybe), and that gets us to the crux of the problem.

The standard library specification deliberately gives some leeway to implementers when it comes to member functions. Specifically:

- A member function signature with default parameters may be replaced by "two or more member function signatures with the equivalent behavior."

- A member function signature may have additional defaulted parameters.

Aye, and there, in the second item, is the rub: Those pesky "might-be-there-or-might-not," "now-you-see-them- now-you-don't" extra parameter critters -- for short, let's call them "peekaboo" parameters -- are what cause our problem in this case.

Much of the time, any extra implementation-specific defaulted "peekaboo" parameters just go unnoticed; for example, when you call a member function you'll get the default values for the peekaboo parameters, so you don't need to ever be aware that the library implementer has thrown a few extra parameters on the end of the member function's signature. Unfortunately, such possible extra parameters do become very noticeable if you need to be sure of the exact signature of the member function -- such as when trying to use mem_fun. Note that this is true even if your compiler deduces template arguments correctly, because of two potential problems:

1. If the member function in question actually takes a parameter and you didn't expect one, you need to write something like bind2nd to get rid of it. Of course, now your code won't work on implementations that tack on an extra parameter of a different type, or none at all -- but, hey, your code wasn't portable anyway, right?

2. If the member function in question actually has two or more parameters (even if they're defaulted), you can't use it with mem_fun at all. Bummer -- but again, your code wasn't portable anyway, right?

[Note: There's actually yet another final problem lurking here. As currently specified, the standard mem_fun adapters only work with const member functions, and vector<int>::clear() is a non-const member function. It seems to be clear that mem_fun was intended to work with non-const member functions, too, and presumably that will soon be addressed in a Technical Corrigendum to the C++ standard and by library implementers (who can do it even in advance of a TC; after all, doing it would be just another extension).]

In practice, though, the problem may not be all that bad. I don't know whether library implementers widely avail themselves of the leeway to add extra parameters, or intend to do so in the future. To the extent that they don't do so, you won't encounter the above difficulties much in practice.

Unfortunately, though, that's not quite the end of the story. Finally, consider a more general consequence of this leeway:

Use Pointers To Member Functions With Anything, Except the Standard Library

Alas, there's an even more basic issue: It is impossible to portably create a pointer to a standard library member function, period.

After all, if you want to create a pointer to a function, member or not, you have to know the pointer's type, which means you have to know the function's signature. Because the signatures of standard library member functions are impossible to know exactly -- unless you peek in your library implementation's header files to look for any peekaboo parameters, and even then the answer might change on a new release of the same library -- the bottom line is that you can't reliably form pointers to standard library member functions and still have portable code.

Conclusion

Do you think it's a little odd that you can portably use a standard library facility, namely mem_fun, with everything except the standard library itself? Do you think it's odd that you can portably use a language feature, namely a pointer to member function, with everything except the language's own standard library?

If you do, and you care about this, let the committee know by posting to comp.std.c++. You will no doubt generate, and learn and benefit from, much detailed discussion about the benefits you get from the implementer's leeway to alter function signatures and how those weigh against the portability benefits you might get if that leeway should be revoked in the future.

Let your voice be heard, and also listen to the resulting feedback; dialogue is only useful when it's two-way, but when it is two-way it's very useful indeed -- for you the users, and for the committee that serves you.

Copyright © 2009 Herb Sutter