SRobertJames
SRobertJames

Reputation: 9206

C++ Template: typename and function to map to int

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.

Upvotes: 0

Views: 2643

Answers (4)

iammilind
iammilind

Reputation: 69988

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

Upvotes: 0

CAdaker
CAdaker

Reputation: 14701

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();
}

Upvotes: 2

myaut
myaut

Reputation: 11504

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;

Upvotes: 0

user1084944
user1084944

Reputation:

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)

Upvotes: 0

Related Questions