3

I would like to create a template using a type, as well as passing a function which has a template argument which contains that type.

Here is what I currently have:

#include <iostream>

void fooRef(int& ref) { std::cout << "In ref" << std::endl; }
void fooPtr(int* ptr) { std::cout << "In ptr" << std::endl; }

template<typename T, typename F>
void Print(T arg, F func) {
    //DoABunchOfStuff();
    func(arg);
    //DoSomeMoreStuff();
}

int main() {
    int x = 5;
    int& ref = x;

    Print<int*, void(*)(int*)>(&x, &fooPtr);
    Print<int&, void(*)(int&)>(ref, &fooRef);
}

This works, but I feel like there may be some extra verbosity to the caller of the function. Ideally I want the call to look something like:

Print<int*, fooPtr>(ptr);
Print<int&, fooRef>(ref); 

Is there a way to simplify the calls to the Print function?

Andrew
  • 1,355
  • 2
  • 13
  • 28
  • 2
    Drop the template parameters in the Print calls: `Print(&x, &fooPtr);` and `Print(ref, &fooRef);` –  Sep 26 '16 at 17:11

3 Answers3

3

Is there a way to simplify the calls to the Print function?

Yes. What you do is not specify the template types at all. Function templates go through a process call template argument deduction. In this process the parameters passed to the function have their type deduced and the compiler tries to match it up to the template parameters. if it works then the function is stamped out and the compiler continues on. So for you code if we used

int main() {
    int x = 5;
    int& ref = x;

    Print(&x, &fooPtr);
    Print(std::ref(ref), &fooRef);
}

Then we get

In ptr
In ref

Live Example

In the second call I used std::ref so it would actually pass ref by reference. Otherwise it would make a copy of whatever ref refers to.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
1

You can simplify it by using template argument deduction, mixed with variadic templates. Here's how one could do it:

template<typename FunctionType, typename... ArgumentTypes>
void Print(FunctionType function, ArgumentTypes... args) {
    function(args...);
}

However, if you want to support references correctly, you'll have to resort to forwarding references, which let compiler deduce not only the type of the argument sent, but it's value type too.

template<typename F, typename... Args>
void Print(F function, Args&&... args) {
    function(std::forward<Args>(args)...);
}

Everything seems fine now, but you still have to deal with your function accepting any arguments. Restricting your function to accept only arguments that will make the call valid can be done using trailing return type:

// instead of specifying the return type before the function name,
// let's place it at the end and use `decltype` to deduce the right type:
template<typename F, typename... Args>
auto Print(F function, Args&&... args) -> decltype(function(std::declval<Args>()...)) {
    return function(std::forward<Args>(args)...);
}

With this code, you'll have clearer errors from your compiler:

void func1(int) {}

Print(func1, 1); // works!
Print(func1, 1, 7); // error: no such function to call

If you still want void as your return type and still restrict your function, you can rely on void_t for that:

// void_t definition
template<typename...>
using void_t = void;

template<typename F, typename... Args>
auto Print(F function, Args&&... args) -> void_t<decltype(function(std::declval<Args>()...))> {
    function(std::forward<Args>(args)...);
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
1

Yes, your ideal syntax is easy:

void fooRef(int& ref) { (void)ref; std::cout << "In ref" << std::endl; }
void fooPtr(int* ptr) { (void)ptr; std::cout << "In ptr" << std::endl; }

template<class T, void(*func)(T)>
void Print(T arg) {
  //DoABunchOfStuff();
  func(std::forward<Arg>(arg));
  //DoSomeMoreStuff();
}

int main() {
  int x = 5;
  int& ref = x;

  Print<int*, fooPtr>(&x);
  Print<int&, fooRef>(ref);
}

Live example.


(void)ref and (void)ptr added to suppress unused variable warnings. std::forward<T> used to make some corner cases more efficient. I would also advise blocking deduction on T with:

template<class T>struct tag_t{using type=T;};
template<class T>using block_deduction=typename tag_t<T>::type;

template<class T, void(*func)(T)>
void Print(block_deduction<T> arg) {

which will force callers to pass T in explicitly, just because I think deducing T here makes the code a bit brittle (it has to exactly match the signature of func).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524