Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Conversations: The Bind That Ties


August 2001 C++ Experts Forum/Conversations


Despite the imminent arrival of the Asian fleet, which our senior officers had not yet commented on publicly, life went back to nearly a steady usualness. Rumors started and grew, and excavation and research of the alien artifacts continued.

Jeannine and I were wrestling with a burst pipe junction. One branch of a "Y" pipe had burst, and water was gushing out furiously, drenching her. "Do something!" she yelled, frustrated, trying to hold a manual seal over it to stem the flow.

I ran for a clamp to bind the seal with and quickly came back to tie the seal down. As the clamp tightened into place around the seal, the water stream turned into a trickle and then stopped. It held. Now that one of the two branches of the "Y" pipe had been restricted, the water was flowing only through the other.

"I suppose this reminds you of your first job, too," said Jeannine, toweling her matted hair.

I grinned. "As a matter of fact, it does. Tying down one too many options, leaving only one..."


The Guru was leading a code review of some code I had recently checked in. All was fine, until she reached this function:

void f(std::vector<int> &vect)
{
  std::vector<int>::iterator firstOne;
  for (firstOne = vect.begin();
     firstOne != vect.end();
     ++firstOne)
  {
    doSomething(*firstOne, "Some string literal");
  }
}

The Guru looked across the meeting room table at me. "My child, this function is not necessary. You can achieve the same effect using the blessed for_each function." A co-op student was participating in the session, so the Guru was in her full act. I recognized — and sympathized with — the look of terror on the poor student's face.

"Um," I ummed, "yeah, I tried that, but for_each needs a function that takes only one parameter. I had to pass doSomething two parameters, so I couldn't use it with for_each."

That earned me a cocked eyebrow. "Did you try using an adaptor such as bind1st?"

"Uh," I uhhed, "well, bind1st has always looked a little complex, and I never could figure out what it meant to 'bind' the argument, so I've kind of ... well, ignored it."

I expected the Guru to fix me with one of her disapproving schoolmaster stares. Instead, she nodded calmly, a hint of a smile on her lips. "My child, you must not be afraid to confront those things you do not understand. Only by acknowledging your ignorance, and confronting it, will you learn." She paused a moment in thought.

"Let us work this from the beginning," she said. "The bind1st and bind2nd functions — for simplicity I will call them the bind functions — allow you to adapt a binary function object for use as a unary function object."

"So," I said slowly, "what you're saying is, because doSomething takes two parameters, it's a binary function, which means I can use bind1st on it to convert it into a unary function, right?"

"Very good, my apprentice. You are on the correct path, but you are not quite there yet. I said that the bind functions allow you to adapt a binary function object. Specifically, the function objects that the bind functions use must be derived from binary_function, or must provide the same typedefs as binary_function. Now, with that information, how would you revise your writings to use a functor?"

In some ways, I found the Guru's Socratic method more intense and stressful than any of my university exams. Feeling the pressure of her gaze, I began carefully writing on the whiteboard:

class doSomething : public std::binary_function
{

I heard the Guru clear her throat. I turned to her, and she handed me a copy of Stroustrup. I quickly looked up binary_function:

template <class Arg, class Arg2, class Res>
struct binary_function {
  typedef Arg first_argument_type;
  typedef Arg2 second_argument_type;
  typedef Res result_type;
}; <a href="#1">[1]</a>

"Okay, only one false start," I thought and revised my declaration:

class doSomething :
  public std::binary_function<int, const char *, void>
{
public:
  void operator()(int intVal, const char *c)
  {
    // ... whatever ...
  }
};

I turned to the Guru.

"Well done," she nodded her approval. "Now all that remains is to use the bind function. It is quite simple. An expression such as bind1st(operation, value) tells the compiler to bind value to the first parameter of operation. In other words, whenever the compiler calls operation in the for_each function, it passes value as the first parameter and the dereferenced iterator for the second parameter, like this." She took the marker and wrote on the board:

operation(value, *iterator)

"Think of it this way," the Guru continued. "With bind1st the first parameter is tied down to a specific value and cannot change. Only the second parameter changes, being passed each item in the range being traversed. Thus, the binary functor can now be used as a unary functor."

"That's it?" I blinked. "That's all there is to it?"

The Guru nodded. "Yes, my child, it truly is that simple. bind2nd behaves analogously, telling the compiler to bind the value given to the second parameter. Now, show me how you would replace or rewrite your function f."

I wrote on the whiteboard:

std::for_each(vect.begin(), vect.end(), 
  bind2nd(doSomething(), "Hi"));

She nodded. "Very good, my apprentice. You realized that the first parameter to bind2nd must refer to an instance of your functor, so you created an unnamed temporary object." I just smiled — I wasn't about to let on that the parentheses were unintentional, actually sloppy writing on my part. "When we have finished our code review," she continued, "I want you to write a small test program to cement your understanding."

After the code review was complete, I returned to my cubicle and typed in a small program. After some minor refinements, I ended up with something like this:

#include <vector>
#include <iostream>
#include <functional>
#include <iterator>
#include <algorithm>

void doSomething(int i, const char *c);

// My First Binary Functor
// First, I'm going to pretend doSomething
// is in a library and I don't have its source.

// Portability note: MSVC6 does not like 'void'
// return types in templates, so change
// operator() to return a value (true, for
// example) under MSVC6
struct doSomethingWrapper : public 
  std::binary_function<int, const char *, void>
{
  void operator()(
    int iValue, const char *cValue) const
  {
    doSomething(iValue, cValue);
  }
};

// Now, a functor built "in-house"
// Same portability note.
struct doSomethingDirect : public 
  std::binary_function<int, const char *, void>
{
  void operator()(
    int iValue, const char *cValue) const
  {
    std::cout << cValue 
          << " " 
          << iValue 
          << ". " ;
  }
};

// A helper template, 'cause I'm lazy and it
// reduces typing
template <class Collection, class Function>
Function for_all(
  Collection &c, const Function &f) 
{
  return std::for_each(c.begin(), c.end(), f);
}

int main()
{
  // First, set up the vector.
  std::vector<int> vect;
  for (int i=0; i<10; ++i) {
    vect.push_back(i);
  }

  for_all(
    vect, std::bind2nd(doSomethingWrapper(), "Wrapper:"));
  std::cout << "\n";
  for_all(
    vect, std::bind2nd(doSomethingDirect(), "Direct:"));
  return 0;
}

// My ingenious third-party-library function
void doSomething(int i, const char *c)
{
  std::cout << c << " " << i << ". " ;
}

"And I threw in a container-based algorithm into the bargain. Ingenious," I congratulated myself badly, thinking I was the first to come up with such a novel idea. Of course, I wasn't.


"Ingenious here too," I sighed, tapping the clamped-down branch of the pipe, "except that in this case we wanted both branches, and we'll have to replace the whole thing..."

We looked at each other.

"Tomorrow," said Jeannine, and shook more water out of her uniform.

Note

[1] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition (Addison-Wesley, 1997).

Jim Hyslop is a senior software designer at Leitch Technology International Inc. He can be reached at [email protected].

Herb Sutter is an independent consultant and secretary of the ISO/ANSI C++ standards committee. He is also one of the instructors of The C++ Seminar (<www.gotw.ca/cpp_seminar>). Herb can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.