Archived

This topic is now archived and is closed to further replies.

Kyo

STL remove_if

Recommended Posts

class CData { ... int value; } now if I have a list of CData how do I use remove_if to only remove data with a certain value? The problem is that the whole adaptable functors bind2nd compose2 and whatnot explanations from sgi''s STL site are about as helpful as a book in egyptian hieroglyphs and all I want to do is remove unique objects in a list, so I can''t use remove(). And the problem with a function object is you don''t instantiate it as far as I can tell, you just pass it like remove_if(functor()) so I can''t tell it what value to compare with to return true. And assuming the value to compare with is 3 the following still doesn''t compile:
  
struct isEqualToValue : public unary_function<data, bool> 
{
	bool operator() (data &rhs) { if (rhs.myValue == 3) { return true; } else { return false; } }
};

L.remove_if(isEqualToValue());
  
Thanks for reading through (and hopefully enlightening me with a reply )

Share this post


Link to post
Share on other sites
well, that code looks fine on an initial read (though there''s a way to bind "3" to the functor at runtime... pass it into the constructor). What error(s) are you getting?


How appropriate. You fight like a cow.

Share this post


Link to post
Share on other sites
It''s possible to do list.remove(isEqualToValue(3))?

The error is:


  
C:\Program Files\Microsoft Visual Studio\VC98\My Projects\Tests\Linked Lists\Main.cpp(82) : error C2664: ''remove_if'' : cannot convert parameter 1 from ''struct isEqualToValue'' to ''class std::binder2nd<struct std::not_equal_to<class data> >''
No constructor could take the source type, or constructor overload resolution was ambiguous


Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Why not have the functor body just be


return rhs.myValue == 3;


Also as Sneftel said, just parameterize the constructor with the value you want to compare against. I think you''re confusing the syntax for constructing an object without a name and the ()-operator used in the functor. If you have a class named A, A() creates an unnamed instance of the object. If you have an instance of A named a, a() invokes the functor method body.

Share this post


Link to post
Share on other sites
quote:
Original post by Anonymous Poster
Why not have the functor body just be


return rhs.myValue == 3;


Also as Sneftel said, just parameterize the constructor with the value you want to compare against. I think you''re confusing the syntax for constructing an object without a name and the ()-operator used in the functor. If you have a class named A, A() creates an unnamed instance of the object. If you have an instance of A named a, a() invokes the functor method body.


You''re right i keep forgetting you can do return (condition) and not just return (value)...

I''d like to get the above functor working before I parameterize it though.

Share this post


Link to post
Share on other sites
you've called your class CData, yet you're parameterising your function with 'data'?

Here, watch. This compiles.


    
include <list>
#include <functional>

using namespace std;

struct MyClass {
int myValue;
};

struct isEqualToValue : public unary_function<MyClass, bool>
{
bool operator() (MyClass &rhs) { return rhs.myValue == 3; }
};

int main()
{
list<MyClass> L;
L.remove_if(isEqualToValue());
return 0;
}

I leave the runtime binding of the number as an exercise to the reader. (hint: it involves adding a member variable to isEqualToValue)
How appropriate. You fight like a cow.

[edited by - sneftel on April 29, 2003 9:07:41 PM]

Share this post


Link to post
Share on other sites
quote:
Original post by Sneftel
you've called your class CData, yet you're parameterising your function with 'data'?

Here, watch. This compiles.


[edited by - sneftel on April 29, 2003 9:07:41 PM]




ok I created a new blank win32 console application project added a new .cpp and cut and paste the above code and lo and behold the same error message!


     
--------------------Configuration: test - Win32 Debug--------------------
Compiling...
main.cpp
c:\program files\microsoft visual studio\vc98\my projects\tests\test\main.cpp(20) : error C2664: 'remove_if' : cannot convert parameter 1 from 'struct isEqualToValue' to 'class std::binder2nd<struct std::not_equal_to<struct MyClass> >'
No constructor could take the source type, or constructor overload resolution was ambiguous
Error executing cl.exe.

test.exe - 1 error(s), 0 warning(s)



Is there something silly I'm forgetting to do? It compiled fine on sneftel's machine so it must be something else...

edit: And I just called CData for the example above, the class was called data in the actual code.

edit2: weird tag behaviour..



[edited by - Kyo on April 29, 2003 9:22:55 PM]

[edited by - Kyo on April 29, 2003 9:24:07 PM]

Share this post


Link to post
Share on other sites
What version of Visual C++ are you using? Could be a problem with their STL. You may want to consider getting STLPort.


How appropriate. You fight like a cow.

Share this post


Link to post
Share on other sites
quote:
Original post by Sneftel
What version of Visual C++ are you using? Could be a problem with their STL. You may want to consider getting STLPort.


How appropriate. You fight like a cow.


Oh yeah thought I mentioned it already i''m using VC++ 6.

Share this post


Link to post
Share on other sites
Try changing unary_function<data, bool>
to unary_function<CData, bool>


This is a slightly more advanced approach:


  
#include <boost\compose.hpp>

#include <algorithm>

#include <list>


template<typename Class, class Alloc, class MemberType>
void RemoveByMember(std::list<Class, Alloc>& list, MemberType Class::* mem_ptr, MemberType value)
{
list.remove_if(
boost::compose_f_gx(
std::bind1st(std::equal_to<MemberType>(), value),
MKH::STL::mem_accessor(mem_ptr)
)
);
}

typedef std::list<CData> mylist_t;
mylist_t list;
RemoveByMember(list, &CData::value, 3);


//mem_accessor

  
namespace MKH
{
namespace STL
{

template<class Class, typename Result>
struct mem_accessor_t : std::unary_function<Class, Result>
{
typedef Result Class::* MemPtr;
mem_accessor_t(MemPtr mem_ptr) : mem_ptr(mem_ptr)
{}
MemPtr mem_ptr;
Result& operator()(Class& c) const
{
return c.*mem_ptr;
}
const Result& operator()(const Class& c) const
{
return c.*mem_ptr;
}
};

template<class Class, typename Result>
mem_accessor_t<Class, Result> mem_accessor(Result Class::* mem_ptr)
{
return mem_accessor_t<Class, Result>(mem_ptr);
}

}
}
[/soruce]

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
MKH, is there any particular reason why you used boost''s compose_f_gx instead of STL''s unary_compose? Also the mem_accessor functor is slick. I''ll have to write that one down.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
Nevermind, I just read the following at the SGI/STL page I was looking at.

"The unary_compose class is an SGI extension; it is not part of the C++ standard."

Share this post


Link to post
Share on other sites
You could use binders with your class if you create a class comparision overload friend function.


#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

class CData
{
public:
int value;

friend bool operator==(const CData &lhs, const CData &rhs);
};

bool operator==(const CData &lhs, const CData &rhs)
{
return lhs.value == rhs.value;
}

int main()
{
CData one, two, three;
list L;
list::iterator p, endp;
one.value = 1;
two.value = 2;
three.value = 3;
L.push_back(one);
L.push_back(two);
L.push_back(three);
endp = remove_if(L.begin(),L.end(),bind2nd(equal_to(),two));
for(p = L.begin(); p != endp; p++)
{
cout << p->value << " ";
}
cout << endl;
return 0;
}


If you want to test on just the integer value you could do:

#include <iostream>
#include <list>
#include <algorithm>

using namespace std;

class CData
{
public:
int value;

};

class isEqualToValue: unary_function
{
public:
int value;
result_type operator() (argument_type &o)
{
if (o.value == value) { return true;}
else { return false; }
}
};


int main()
{
CData one, two, three;
isEqualToValue condition;
list L;
list::iterator p, endp;
one.value = 1;
two.value = 2;
three.value = 3;
L.push_back(one);
L.push_back(two);
L.push_back(three);
condition.value = 3;
endp = remove_if(L.begin(),L.end(),condition);
for(p = L.begin(); p != endp; p++)
{
cout << p->value << " ";
}
cout << endl;
return 0;
}



[edited by - Interim on April 29, 2003 12:19:35 AM]

Share this post


Link to post
Share on other sites
There's no use, the list implementation of remove_if requires a not_equal_to predicate in this case. The compiler isn't able to convert back and forth between that predicate and one you might derive and pass off as the templated type to binder2nd either.

If only the list iterators implemented addition and subtraction operators, you'd be set with the regular algorithmic remove_if! Wouldn't be too hard, just successive increments / decrements until you got where you wanted, and if it goes outside bounds, it sets it to the end or just before the beginning.

Oh well, you're stuck using a more complicated procedure, at least for lists.

SGI has it more properly implemented, where it can take any unary function. That's more like it if you ask me

[edited by - Zipster on April 30, 2003 2:41:27 AM]

Share this post


Link to post
Share on other sites
To sum up the above my options are

a) Learn STL properly and use a more complex method
b) Get STLPort (at the risk of introducing more problems as I have no idea what STLPort is...) and sneftel''s code will compile properly

Share this post


Link to post
Share on other sites
STLPort is an implementation of STL that is much, much better than the implementation shipped with MSVC++. You seem to be fine with this STL stuff at this point; AFAICT, you''re merely shackled by an inferior implementation.


How appropriate. You fight like a cow.

Share this post


Link to post
Share on other sites