0

I'm writing a C++ template that needs two params: typename T, and an arbitrary function that maps T to an unsigned int.

How can I declare and use a template that can do that? I'd like to keep it simple, so that any dumb function can be used.

UPDATE:

Here is an example of what I'd like to do:

 template<typename T, function f> // f has signature: unsigned int f(T);
 class SortedContainer {
      ...
 }

And, in this file:

 unsigned int weight(Package p) { return p.w; }

 SortedContainer<Package, &weight> sc;

UPDATE upon writing code

Based on the answers, I tried writing code, but it won't compile. Or rather, the template will compile, but not the test which invokes it.

The template code looks like this:

template<typename T, typename f>
class C {
...f(T)...
...

The invocation code looks like:

struct S {
int operator()(const int n) {
    return n; // Dummy test code
}
 };

 ...C<int, S>&...

The error message is:

 error: no matching function for call to 'S::S(const int&)'
 note: candidates are:
 note: S::S()

It seems like it's trying to use S's constructor for some reason, as opposed to using the operator() which I want it to do.

The purpose of the f parameter is that the SortedContainer needs to be able to position T by an integer value. T is not necessarily an integer or even Comparable, so the caller, when instantiating a SortedContainer, needs to pass not only type T, but a function f to transform T to an integer.

SRobertJames
  • 8,210
  • 14
  • 60
  • 107
  • 1
    What do you mean by saying "maps T to an unsigned int"? Can you show prototype of that function? – myaut Apr 12 '15 at 08:46
  • @myaut updated accordingly – SRobertJames Apr 12 '15 at 09:09
  • The question is still not clear. What is the purpose of having `Function F` (or whatever) as the 2nd parameter? If you tell the necessity of it then, may be we can omit that! – iammilind Apr 13 '15 at 13:06
  • @SRobertJames, I tried to look hard but still couldn't understand the need of `F`. Why can't you decide a generic function name like `uint to_uint (Type t);` and just keep overloading it for various types, which will be used as the first `typename` to the `class SortedContainer`? – iammilind Apr 14 '15 at 05:01
  • 1
    @iammilind Perhaps you are right! Can you post an answer showing how to do this? An example would be most helpful. – SRobertJames Apr 14 '15 at 22:47

4 Answers4

2

The common way of doing this is to accept a general type F for the function. This will allow any kind of function-like object, whether it is a function pointer or a class object with an overloaded operator(). So:

template<class T, class F>
class SortedContainer {
    // ...
}

Compare with things like std::map which does exactly this.

The disadvantage of this is that you cannot control what the prototype of the function is. This may or may not be a problem. One way is just to use it as if it was T-to-unsigned int and rely on the fact that the type system will catch any errors at the point of use.

Another way would be to verify the constraint with some kind of type trait. An example:

static_assert(std::is_same<unsigned int,
                           typename std::result_of<F(T)>::type>::value,
              "Function must be T-to-unsigned int");

Edit: I wrote a small example to convince myself i got the assert right, might as well post it. Here, using A will compile OK but B will fail the assertion.

#include <type_traits>

template<class T, class F>
class SortedContainer {
    static_assert(std::is_same<unsigned int,
                               typename std::result_of<F(T)>::type>::value,

                  "Function must be T-to-unsigned int");
};

struct A {
    unsigned int operator()(double) { return 0; }
};
struct B {
    double operator()(double) { return 0; }
};

int main() {
    SortedContainer<double, A> a;
    SortedContainer<double, B> b;
}

Based on your other edit:

Note that the templated type F only captures the type of the function. You still need an object of this type - the actual function - to call. Again, compare with std::map which first is templated to take a comparator type, and then has a constructor that takes an object of this type. This is true even if you use a normal function - the type will be SortedContainer<T, unsigned int (*)(T)>, but you would somehow need to pass the actual function pointer into the container (probably through the constructor).

Something like this:

template<class T, class F>
class SortedContainer {
public:
    SortedContainer(F f = F()): func(f) {}

    void foo() {
        // ...
        func();
        // ...
    }
private:
    F func;
};

struct A {
    unsigned int operator()() { return 0; }
};

int main() {
    A a;
    SortedContainer<double, A> c(a);
    c.foo();
}
CAdaker
  • 14,385
  • 3
  • 30
  • 32
  • 1
    Depending on what you're after, you may instead want to check that the function return type is implicitly convertible to `unsigned int`. (`std::is_convertible` can be used instead of `std::is_same` for that, just swap the template arguments.) –  Apr 12 '15 at 09:40
  • Indeed. It depends on how strict the prototype constraint is. – CAdaker Apr 12 '15 at 09:41
0

It's as you said, pretty much:

template< typename T, unsigned int f(T) >
struct SortedContainer;
...
SortedContainer<Package, weight> sc;

if you actually wanted the argument to be a function pointer rather than a function,

template< typename T, unsigned int (*f)(T) >

and similarly if you want the argument to be a function reference.

(naturally, this will only work for dumb functions, not for function objects with an operator() operator of the right signature)

  • 2
    The two declarations are equivalent. There's no such thing as a template parameter of function type; it's adjusted to the function pointer type, just like function parameters. – T.C. Apr 12 '15 at 09:22
0

You may use C-style function pointers as @Hurkyl suggests, or std::function which probably can't be template parameters, but I think that idea is wrong.

C++ templates are duck-typed, so STL code in many places (std::unordered_map -> std::hash, std::sort -> std::less) relies on that. I think you should also apply this approach - just ask user to provide specialization for type T:

/* Universal implementation */
template<typename T>
unsigned int sorted_container_weight(T t) { return t; }

template<typename T>
class SortedContainer {
    T t;
public:
    unsigned int somefunc() {
        return sorted_container_weight(t);
    }    
};

template<>
unsigned int sorted_container_weight<Package>(Package p) { return p.w; }

SortedContainer<Package> sc;
myaut
  • 11,174
  • 2
  • 30
  • 62
0

IMO, you don't require a separate template argument for Function F.

template<typename T>  // F not required!
class SortedContainer {
      ...
};

Choose a good name and use that function by overloading it for various cases. e.g. to_uint()
Since you want to map (i.e. relate) a type to an unsigned int (uint), use following function in global scope:

template<typename T>
uint to_uint (const T& t) {
  return t.to_uint();  // Must have `uint to_uint() const` member, else error
}

// Overloads of `to_uint()` for PODs (if needed) 

template<typename T>  // For all kinds of pointers
uint to_uint (const T* const pT) {
  if(pT == nullptr)
    <error handling>;
  return to_uint(*pT);
}

Scenario: For Sorted_Container<X>, whenever to_uint(x) is invoked, then:

  1. If X is a class, then it must have uint to_uint() const method
  2. Else if X is a POD, then you may have to overload to_uint() for that type
  3. Else, the compiler will generate an error
iammilind
  • 68,093
  • 33
  • 169
  • 336
  • 1
    I was going to accept this answer, but realized it won't work for the most common case: where `T` is a pointer. Of course, you could say `T` must support `T->to_uint()`, but then it won't work when T is _not_ a pointer. Any solution? – SRobertJames May 26 '15 at 03:06
  • @SRobertJames, Write a single overload for all the pointer types and internally call the actual `to_uint()`. See the updated answer with the code. – iammilind May 26 '15 at 07:06
  • Bravo! Why did you check for nullptr, can't we just let the null exception bubble up? And, is there any reason yo made the to_uint function a general function, and didn't make it a static method of SortedContainer (ie without repeating the template decorator) – SRobertJames May 26 '15 at 07:40
  • @SRobertJames, In C++ you may not get the null exception, hence better to check `nullptr` wherever you can and handle the error in your way. If we make `to_uint()` part of `SortedContainer` then you have to call it as `SortedContainer::to_uint()` rather than simply `to_uint()`. It's better to keep `to_uint()` inside some namespace such as `Util` and keep along with library files, so that it becomes usable for general purpose. After this you may call it s `Util::to_uint()`. – iammilind May 26 '15 at 11:00