Boost logo

Boost Users :

From: Bjorn.Karlsson_at_[hidden]
Date: 2005-07-17 12:10:16


Hello Jim,

> "Jim Pritts" <jpritts_at_[hidden]> wrote in message
>
> Is it possible to do something like the following:
>
> typedef boost::tuple<double, int, int> element;
>
> std::vector<element> d;
>
> std::vector<element>::iterator i = std::max_element(d.begin(),
> d.end(),
>
> bind(std::max<double>,bind(get<0>,_2),_1));

Good question! The above won't compile, in part because get() requires three
template arguments, so you can't get it's address using &get<0>.
Furthermore, it's not trivial to explicitly instantiate get() correctly or
to bind to tuple's member function get(), which is why I'd suggest creating
a helper class--a function object--for selecting one of the elements of a
tuple. To use it with Boost.Bind without explicitly specifying the return
type, you'd need to do this:

template <typename T,int i> class select
{
public:
  typedef typename boost::tuples::element<i,T>::type result_type;

  template <typename U> result_type operator()(const U& u) const
  {
    // Select the ith element of the tuple T and return it
    return u.get<i>();
  }
};

The required typedef, result_type, that enables Boost.Bind to deduce the
return type of the function call operator, uses a helper class from
Boost.Tuples, the class template element, which retrieves the type of a
tuple element using its index. With a class template like select, you could
use Boost.Bind with max_element like so:

int main()
{
  typedef boost::tuple<double,int,int> element;
  std::vector<element> d;

  // Code for adding elements omitted

  std::vector<element>::iterator i=
    std::max_element(
      d.begin(),
      d.end(),
      boost::bind(std::less<double>(),
        boost::bind(select<element,0>(),_1),
        boost::bind(select<element,0>(),_2)));
}

You'll also note that I've adjusted the binders from the example you gave.
There are three binders in the call to max_element; the two innermost
binders extract the first element of the tuples _1 and _2 (to be substituted
with the actual arguments), and the third binder binds std::less<double>.
The result of all these binders is a binary predicate, which is what
std::max_element expects. Note that in your example, you had used
std::max(), which does not work well as a binary predicate for
max_element().

Although the above version of select works, it's rather inflexible and
sometimes inefficient, because the function call operator always returns a
copy of the ith element of the tuple. What if you need to assign a value to
that element? To enable that, you'll need a new version of select, a class
template that is only parameterized on the element index, and with two
parameterized function call operators that return a copy of the element or a
reference to the element, depending on the constness of the tuple. Using
Boost.Type_traits, it's easy to add a reference to a type without risking
forming a reference to reference (those are currently illegal in C++):

template <int i> class select
{
public:
  template <typename T>
    typename boost::tuples::element<i,T>::type
      operator()(const T& t) const
  {
    // Select the ith element of the tuple t and return it
    return t.get<i>();
  }

  template <typename T>
    typename boost::add_reference<typename
boost::tuples::element<i,T>::type>::type
      operator()(T& t) const
  {
    // Select the ith element of the tuple t and return it
    return t.get<i>();
  }
};

This version of select works fine, but Boost.Bind is no longer able to
deduce the return type of the function call operator(s), so you'll need to
provide that information explicitly when using select:

std::vector<element>::iterator i=
  std::max_element(
    d.begin(),
    d.end(),
    boost::bind(std::less<double>(),
      boost::bind<double>(select<0>(),_1),
      boost::bind<double>(select<0>(),_2)));

If you think we're all done here, think again. ;-)

When using Boost.Lambda, which also offers binders, there's an extended
system for deducing return types. It comes in the form of a nested class
template called sig that is responsible for providing the correct return
type depending on which function call operator is being called, i.e.,
depending on the actual arguments to the function call operator(s). That is
precisely the kind of mechanism that you need to solve the problem with the
multiple function call operators of select. The following version of select
is the most user-friendly version, but it is a little more complex. It uses
Boost.Mpl for a compile-time if, Boost.Type_traits for adding a reference to
the element type, Boost.Lambda's return type deduction system, and of course
Boost.Tuples to operate on the tuple and retrieve the ith element's type:

template <int i> class select
{
public:
  // Provide the return type
  template <typename Arg> class sig
  {
    typedef typename boost::tuples::element<1,Arg>::type argument_type;
    typedef typename boost::tuples::element<i,argument_type>::type
element_type;
  public:
    // Select the correct return type; depends on the
    typedef typename boost::mpl::if_<boost::is_const<element_type>,
      element_type,
      typename boost::add_reference<element_type>::type>::type type;
  };

  template <typename T>
    typename boost::tuples::element<i,T>::type
      operator()(const T& t) const
  {
    // Select the ith element of the tuple t and return it
    return t.get<i>();
  }

  template <typename T>
    typename boost::add_reference<typename
boost::tuples::element<i,T>::type>::type
      operator()(T& t) const
  {
    // Select the ith element of the tuple t and return a reference to it
    return t.get<i>();
  }
};

Using this version of select and Boost.Lambda's binders, you'll have both an
easy-to-use interface and a powerful way of selecting tuple elements, both
for lvalues and rvalues. Here's a complete program to demonstrate your
example again. Note that the class template select requires you to include
the headers "boost/tuple/tuple.hpp", "boost/type_traits.hpp", and
"boost/mpl/if.hpp". For Boost.Lambda's bind(), you'll need to include
"boost/lambda/bind.hpp".

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include "boost/tuple/tuple.hpp"
#include "boost/type_traits.hpp"
#include "boost/mpl/if.hpp"
//#include "boost/lambda/bind.hpp"

template <int i> class select
{
public:
  // Provide the return type
  template <typename Arg> class sig
  {
    typedef typename boost::tuples::element<1,Arg>::type argument_type;
    typedef typename boost::tuples::element<i,argument_type>::type
element_type;
  public:
    // Select the correct return type; depends on the
    typedef typename boost::mpl::if_<boost::is_const<element_type>,
      element_type,
      typename boost::add_reference<element_type>::type>::type type;
  };

  template <typename T>
    typename boost::tuples::element<i,T>::type
      operator()(const T& t) const
  {
    // Select the ith element of the tuple t and return it
    return t.get<i>();
  }

  template <typename T>
    typename boost::add_reference<typename
      boost::tuples::element<i,T>::type>::type operator()(T& t) const
  {
    // Select the ith element of the tuple t and return a reference to it
    return t.get<i>();
  }
};

int main()
{
  typedef boost::tuple<double,int,int> element;
  std::vector<element> d;

  // Code for adding elements omitted

  using namespace boost::lambda;

  std::vector<element>::iterator i=
    std::max_element(
      d.begin(),
      d.end(),
      bind(std::less<double>(),
        bind(select<0>(),_1),
        bind(select<0>(),_2)));

  std::cout << "max element: " << (*i).get<0>();
}

Regards,
Bjorn Karlsson


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net