views:

664

answers:

5

I've been playing about with functors in C++. In particular, I've got a vector of pairs I'd like to sort by the first element of the pair. I started off writing a completely specialised functor (i.e. something like "bool MyLessThan(MyPair &lhs, MyPair &rhs)"). Then, just because this sort of stuff is interesting, I wanted to try writing a generic "Apply F to the first elements of this pair" functor. I wrote the below, but g++ doesn't like it. I get:

error: type/value mismatch at argument 2 in template parameter list for 'template struct Pair1stFunc2' error: expected a type, got 'less'

#include <algorithm>
#include <functional>
#include <utility>
#include <vector>

template <class P, class F>
struct Pair1stFunc2
{
    typename F::result_type operator()(P &lhs, P &rhs) const
    { return F(lhs.first, rhs.first); }

    typename F::result_type operator()(const P &lhs, const P &rhs) const
    { return F(lhs.first, rhs.first); }
};

typedef std::pair<int,int> MyPair;
typedef std::vector<MyPair> MyPairList;

MyPairList pairs;

void foo(void)
{
    std::sort(pairs.begin(),
              pairs.end(),
              Pair1stFunc2<MyPair, std::less>());
}

Can anyone shed any light on what I'm doing wrong here? I know this is as slightly artificial example, but I'd like to know what's going on, if only to improve my STL-fu.

+3  A: 

Note that std::less is itself a template and you do not specify the template template parameter when you call it from with foo() function's sort! Here less is an incomplete type and hence the problem.

dirkgently
+2  A: 

You need to specialize std::less with the comparison type you're using.

Pair1stFunc2<MyPair, std::less<int> >()

will do the trick. Within your own operator() you'll also need to instantiate an object of the comparison type, since you can't just call the class directly. E.g. change

return F(lhs.first, rhs.first);

to

F func;
return func(lhs.first, rhs.first);

You could also move the specialization into the functor, as another answer suggests.

Nik
He's not actually using std::less with MyPair, though, but std::less<int> instead.
Quite right, brain error on my part
Nik
+6  A: 

To expand on dirkgently's answer, here's an example of what might work as you intend:

template <typename T, template <typename> class F>
struct Pair1stFunc2
{
    template <typename P>
    typename F<T>::result_type operator()(P &lhs, P &rhs) const
    { F<T> f; return f(lhs.first, rhs.first); }

    template <typename P>
    typename F<T>::result_type operator()(const P &lhs, const P &rhs) const
    { F<T> f; return f(lhs.first, rhs.first); }
};

void foo(void)
{
    std::sort(pairs.begin(),
              pairs.end(),
              Pair1stFunc2<int, std::less>());
}

Note that it works, but it might not be exactly what you had in mind.

http://codepad.org/54PXQSoL
Comptrol
+1 for non-specified templates as template parameters.
rstevens
This works, but template template parameters are actually unnecessary here because you only ever refer to F as "F<T>" -- see Martin York's answer, which will also work for non-templated comparators.
j_random_hacker
boost mpl lambda expression comes handy here. just pass std::less<_> you could write "typename F" then and do typename mpl::apply<F, int>::type f; instead to get a std::less<int> which i find very nice, especially when you have some template that could take additionally optional arguments (like std::vector template), you just could pass std::vector<_>
Johannes Schaub - litb
+1  A: 

The simplest solution would be to state what you want as an argument, a function with a suitable signature:

template<typename P, bool (*F)(P,P)> struct Pair1stFunc2 { ... }

In this case, passing a function template as the second argument will cause overload resolution to be done on it with P,P as argument types. This works because you move the overload resolution out of struct Pair1stFunc2::operator()

You also want the possibility of passing in a functor, but those do need to be passed as a template type argument and then created inside operator() :

typename F::result_type operator()(const P &lhs, const P &rhs) const
{ return F()(lhs.first, rhs.first); }

Here, F is the functor type and F() an instance of that functor.

The third case is already covered earlier, the functor template. std::less is such a template. In that case, you need a template template argument.

MSalters
Basically correct, but I'd say that instances of std::less<T> are functors. A functor can be called using the syntax F(argument-list). You can't call std::less like that.
MSalters
(deleted my comment because it referred to the answer before it was edited)
James Hopkin
Are you proposing two separate syntaxes for Pair1stFunc2::operator()(lhs, rhs), one for function pointers, another for functors? Granted, your solution using a function pointer template parameter should be smaller and faster for that case, but you can use a single syntax by having Pair1stFunc2 take two template *type* parameters (P and F) and store a member f of type F (this can be populated in a 1-arg ctor for the function pointer case). Then operator()(lhs, rhs) can say "return f(lhs.first, rhs.first);".
j_random_hacker
Actually proposing three templates, but they would have similar syntaxes. Pair1stFunc2<P, function>, Pair1stFunc2<P, functor_type> and Pair1stFunc2<P, functor_template>. Their operator()s are quite similar.
MSalters
@MSalters: Ah thanks. +1 for the first of the three which should definitely give a speed-up for the function pointer case (a direct, possibly even inlined call instead of an indirect call).
j_random_hacker
+2  A: 

Similar to unwesen. But you don't need to use template templates.

#include <algorithm>
#include <functional>
#include <memory>
#include <vector>

typedef std::pair<int,int> MyPair;
typedef std::vector<MyPair> MyPairList;
MyPairList pairs;


// Same as original.
template <typename T,typename F>
struct Pair1stFunc2
{
    template <typename P>
    typename F::result_type operator()(P &lhs, P &rhs) const
    { F f;  // Just need to create an anstance of the functor to use.
      return f(lhs.first, rhs.first); }

    template <typename P>
    typename F::result_type operator()(const P &lhs, const P &rhs) const
    { F f;  // Just need to create an anstance of the functor to use.
      return f(lhs.first, rhs.first); }
};


void foo(void)
{
    std::sort(pairs.begin(),
              pairs.end(),
              Pair1stFunc2<int, std::less<int> >()); // initialize the version of less
}
Martin York
+1. This is more general than unwesel's solution because it will also work for non-templated comparators.
j_random_hacker