Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I get the C++ compiler to deduce T indirectly?

Tags:

c++

templates

My template-fu is rather weak. I have this code:

template<typename T>
void Foo(void(*func)(T*)) { }

void Callback(int* data) { }

int Test()
{
    Foo(Callback);
}

...but I'd like something more readable than C's nasty function pointer syntax of void(*func)(T*).

Someone on my team suggested this:

template<typename T>
struct Types
{
    typedef void Func(T*);
};

template<typename T>
void Foo2(typename Types<T>::Func* func) {}

void Test2()
{
    Foo2(Callback);        // could not deduce template argument for 'T'
    Foo2<int>(Callback);   // ok
}

(I'm still debating whether this is actually more readable, but that's a separate issue.)

How can I help the compiler figure out what T is without needing to explicitly specify it in the caller?

like image 232
scobi Avatar asked Dec 19 '14 12:12

scobi


2 Answers

You can extract T from the function type using a traits class.

template<class F>
struct CallbackTraits;

template<class T>
struct CallbackTraits<void(*)(T)>
{
    typedef T ArgumentType;
};

Your example can be modified like this:

template<typename F>
void Foo(F func)
{
    typedef typename CallbackTraits<F>::ArgumentType T;
}

void Callback(int* data) { }

int Test()
{
    Foo(Callback);
}

This technique is used in the boost type-traits library: http://www.boost.org/doc/libs/1_57_0/libs/type_traits/doc/html/boost_typetraits/reference/function_traits.html

This blog post goes into a bit more detail about the implementation of the technique: https://functionalcpp.wordpress.com/2013/08/05/function-traits/

Unfortunately this approach hides the information in the signature of Foo about the constraints on the argument passed in. In the above example the argument must be a function of type void(T*).

This alternative syntax does the same as the original example while being slightly more readable:

template<typename T>
void Foo(void func(T*)) { }

Another alternative syntax that may be more readable can be achieved using c++11's alias templates as follows:

template<typename T>
using Identity = T;

template<typename T>
void Foo(Identity<void(T*)> func) { }

Unforunately the latest MSVC fails to compile this, reporting an internal compiler error.

like image 155
willj Avatar answered Sep 18 '22 19:09

willj


You won't be able to deduce the type based on a nested name: there is no reason why different instantiations of the outer type won't define an identical inner type. You could use a using alias, though:

template <typename T>
using Function = auto (*)(T*) -> void;

template <typename T>
void Foo(Function<T>) {
}

Personally, I would recommend against using any of that, however: in practice it seems much more advisable to actually take a function object which later allows using object with suitable function call operators to be used. For callbacks it is quite common that you'll need to pass in some auxiliary data. That is, you would either use an unconstrained template or one which takes a type-erased type, depending on what you want to do exactly:

template <typename Fun>
void Unconstrained(Fun fun) {
}
template <typename T>
void TypeErased(std::function<void(T*)> fun) {
}

The unconstrained version has the advantage that it can potentially inline the function call but it has the disadvantage that every function object type creates a new instantiation and that the argument types are likely to vary. The type-erased version effectively has to do something like a virtual function call but there is just one instantiation of the function template (per argument type T, of course).

Admittedly, the type-erased version's type won't be deduced from a function pointer (or any other argument which isn't a std::function<void(X*)>), i.e., you may want to have a forwarding function

template <typename T>
void TypeErased(Function<T> fun) {
    TypeErased(std::function<void(T)>(fun));
}
like image 33
Dietmar Kühl Avatar answered Sep 20 '22 19:09

Dietmar Kühl