6

In general, templates arguments can be abstract classes, as the program below also shows. But it seems that the compare functor in sort must not be abstract. At least the following does not compile with VC++ 11 and on Oracle Studio 12.

#include <vector>
#include <algorithm>


class Functor
{
public:
    virtual bool operator()(int a, int b) const = 0;
};


class MyFunctor: public Functor
{
public:
    virtual bool operator()(int a, int b) const { return true; }
};


int _tmain(int argc, _TCHAR* argv[])
{
    vector<Functor> fv; // template of abstract class is possible
    vector<int> v;
    MyFunctor* mf = new MyFunctor();
    sort(v.begin(), v.end(), *mf);
    Functor* f = new MyFunctor();
    // following line does not compile: 
    // "Cannot have a parameter of the abstract class Functor"
    sort(v.begin(), v.end(), *f); 
    return 0;
}

Now, I wonder whether this is a general property of functor arguments, or does it depend on the STL implementation? Is there a way to get, what I wanted to do?

Kit Fisto
  • 4,385
  • 5
  • 26
  • 43
  • It might be worth your time to research [Boost.Ref](http://www.boost.org/doc/libs/1_51_0/doc/html/ref.html) – andre Sep 13 '12 at 14:39
  • @ahenderson: Good idea - or just `std::ref`, as in `sort(v.begin(), v.end(), std::ref(*f));`. – Kerrek SB Sep 13 '12 at 15:16
  • Conclusion: I understand that the functor is used by value and must therefore be copyable which is not the case for an abstract base classes. The elegant solutions based on C++ 11 using std::ref and/or std::bind cannot be compiled with VC++ 2012 despite its claim to support them. Therefore I am using Michael Andersons approach now. Thanks to Kerrek SB and ahenderson to sorting all this out. – Kit Fisto Sep 14 '12 at 10:20

3 Answers3

13

Functors generally need to be copyable. Polymorphic base classes are generally not copyable, and abstract bases never.

Update: Thanks to the comments by @ahenderson and @ltjax, here's a very simple way to produce a wrapper object that holds your original, polymorphic reference:

#include <functional>

std::sort(v.begin(), v.end(), std::ref(*f));
//                            ^^^^^^^^^^^^

The result of std::ref is a std::refrence_wrapper which is exactly what you need: A class with value semantics that holds a reference to your original object.


The fact that functors get copied throws off lots of people who want to accumulate something inside the functor and then wonder why the results are off. The functor should really take a reference to an external object. To wit:

Bad! Won't work as you expect; the functor may get copied arbitrarily:

struct Func1 {
    int i;
    Func1() : i(0) { }
    void operator()(T const & x) { /* ... */ }
};

Func1 f;
MyAlgo(myContainer, f); 

Good: You provide the accumulator; it's safe to copy the functor:

struct Func2 {
   int & i;
   Func2(int & n) : i(n) { }
   void operator()(T const & x) { /* ... */ }
};

int result;
MyAlgo(myContainer, Func2(result));
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Ok, I begin to understand. Can you explain at what point the copying is done? – Kit Fisto Sep 13 '12 at 15:01
  • @KitFisto: No, it's not specified. Look at your standard library implementation, though, and I'm sure you'll find that the predicate is passed by value, or something like that. – Kerrek SB Sep 13 '12 at 15:15
  • Functors are always passed by value. A const-ref implies `operator()() const`. A non-const-ref prohibits passing it directly as `Func()`. Due to optimization your functor might actually never be copied but it needs this property to comply to the rules. – rtlgrmpf Sep 13 '12 at 15:32
  • VC++ 11 gies me this error, when trying the solution with std::ref: term does not evaluate to a function taking 2 arguments. – Kit Fisto Sep 13 '12 at 15:35
  • @KitFisto try `std::sort(v.begin(), v.end(), std::bind(std::ref(*f), _1, _2);` this should stop the error and get the correct result. – andre Sep 13 '12 at 15:51
  • @ahenderson: Not quite ;-) There are 8 errors now, the first one starts with: error C2171: '!' : illegal on operands of type 'std::_Do_call_ret<_Forced,_Ret,_Funx,_Btuple,_Ftuple>::type' 1> with 1> [ 1> _Forced=false, 1> _Ret=void, 1> _Funx=std::reference_wrapper, 1> _Btuple=std::tuple,std::_Ph<2>>, 1> _Ftuple=std::tuple 1> ] – Kit Fisto Sep 14 '12 at 06:20
  • @KitFisto: It must be a shortcoming of your compiler. The code is fine as far as C++ is concerned (e.g. see this [live example](http://ideone.com/4qKJk)). You can always go for one of the other, more pedestrian solutions if your compiler doesn't support that code... – Kerrek SB Sep 14 '12 at 06:30
5

As Kerrek has said you can't do it directly:

But one level of indirection and you're OK.

struct AbstractFunctor
{
  AbstractFunctor( Functor * in_f ): f(in_f) {}
  // TODO: Copy constructor etc.

  Functor * f;
  bool operator()(int a, int b) const { return (*f)(a,b); }
};

int main()
{
  vector<int> v;
  Functor * mf = new MyFunctor();
  sort(v.begin(), v.end(), AbstractFunctor(mf) );
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
Michael Anderson
  • 70,661
  • 7
  • 134
  • 187
2

As Kerrek and Michael Anderson said, you can't do it directly. As Michael shows, you can write a wrapper class. But there's also one in std:: :

sort(v.begin(),
     v.end(), 
     std::bind(&Functor::operator(),
               mf,
               std::placeholders::_1,
               std::placeholders::_2) );
MSalters
  • 173,980
  • 10
  • 155
  • 350