tags:

views:

168

answers:

4

Searching in the second value of a map i use somthing like the following:

typedef std::map<int, int> CMyList;
static CMyList myList;

template<class t> struct second_equal
{
    typename typedef t::mapped_type mapped_type;
    typename typedef t::value_type value_type;

    second_equal(mapped_type f) : v(f)   {};
    bool operator()(const value_type &a) { return a.second == v;};

    mapped_type v;
};
...    
int i = 7;
CMyList::iterator it = std::find_if(myList.begin(), myList.end(), 
                                    second_equal<CMyList>(i));

Question: How can i do such a find in a single line without supplying a self written template?

+7  A: 

Use a selector to select the first or the second element from the value_type that you get from the map. Use a binder to bind the value (i) to one of the arguments of the std::equal_to function. Use a composer to use the output of the selector as the other argument of the equal_to function.

//stl version
CMyList::iterator it = std::find_if(
    myList.begin(), 
    myList.end(), 
    std::compose1(
        std::bind2nd(equal_to<CMyList::mapped_type>(), i), 
        std::select2nd<CMyList::value_type>())) ;

//Boost.Lambda or Boost.Bind version
CMyList::iterator it = std::find_if(
    myList.begin(), 
    myList.end(), 
    bind( &CMyList::mapped_type::second, _1)==i);
TimW
is it possible to shorten this with boost::bind?
Idan K
Spot on, but so unreadable and messy I would recommend code clean-up in a code review. Isn't there a better way with boost binders ?
Matthieu M.
+1, so much readable with Boost :)
Matthieu M.
Erm, isn't that Boost.Bind, not Lambda?
sbi
@sbi For this example the Boost.Bind syntax is the same as the Boost.Lambda syntax
TimW
I was looking for a plain STL-solution and your first sample actually is it. But i have to admit that i rather stay with my homegrown template than nesting 3 STL calls into a parameter. Its much easier to type as well as to understand.
RED SOFT ADAIR
@TimW: Ah, thank you.
sbi
std::compose1 and std::select2nd are not part of the standard, it's an SGI extension.
Zitrax
For gcc select2nd seems to be in the namespace __gnu_cxx.
Zitrax
A: 

I am going to be off, voluntarily. The problem with lambda's is that (apart from C++0x) you cannot actually use something like _.second at the moment.

Personally, I thus use:

template <class Second>
class CompareSecond
{
public:
  CompareSecond(Second const& t) : m_ref(t) {} // actual impl use Boost.callparams
  template <class First>
  bool operator()(std::pair<First,Second> const& p) const { return p.second == m_ref; }
private:
  Second const& m_ref;
};

Which I combine with:

template <class Second>
CompareSecond<Second> compare_second(Second const& t)
{
  return CompareSecond<Second>(t);
}

In order to get automatic type deduction.

And this way I can just write

CMyList::iterator it = std::find_if(myList.begin(), myList.end(), compare_second(i));

True, it does not use binders.

But at least, mine is readable and easily understandable, which beats the crap out of clever trickery in my opinion.

Note:
actually I went as far as wrapping STL algorithms to take full containers, so it would be:

 CMyList::iterator it = toolbox::find_if(myList, compare_second(i));

which (imho) is clearly as readable as you can get without the auto keyword for type inference.

Matthieu M.
I like the note. I have a header called `container.h` that does this for most of the algorithms in a separate namespace called, you guested it, `container`.
D.Shawley
A: 

You can use Boost Lambda

CMyList::iterator it = std::find_if(
      myList.begin(), myList.end(), 
      boost::lambda::bind(&CMyList::value_type::second, boost::lambda::_1) == i);
skwllsp
A: 

You can turn this problem around and just write your own algorithm and use it instead. This way you are not stuck with writing lots of little functors.

template <typename Iter, typename T>
Iter find_second(Iter first, Iter last, T value) {
    while (first != last) {
        if (first->second == value) {
            return first;
        }
        ++first;
    }
    return first;
}

Note this isn't tested or even compiled.

It seems to me that solving this with binders is just asking for lots of ugly code. What you are really asking for is a new algorithm so just add the algorithm. With that said, I would probably end up implementing something like Matthieu M. came up with.

D.Shawley
I disagree with this approach, nonetheless the name you picked for your algorithm is very misleading. If I didn't look at the source code I would guess it returns the second match of a value.
Idan K
@daniel: didn't even think of that when I named it... it does look a little like something that should mimic `find_first`.
D.Shawley