Reputation: 600
How to write a template that use function as template parameter, and auto deduce other typename by this function's argument type?
void foo(int *p) {}
template<typename T, void (*F)(T*)>
struct bar
{
bar(T* t)
{
F(t);
}
}
int *p;
bar<int, foo> b(p); // both int and foo are required here
how to write a template that supports to use only foo
as argument
bar<foo> b(p);
Upvotes: 2
Views: 662
Reputation: 217293
In C++17, you might do
template <auto> struct bar;
template<typename T, void (*F)(T*)>
struct bar<F>
{
bar(T* t) { F(t); }
};
with usage:
int* p = nullptr;
bar<foo> b(p);
Prior C++17, you might do:
template <typename T, T> struct bar;
template<typename T, void (*F)(T*)>
struct bar<void (*)(T*), F>
{
bar(T* t) { F(t); }
};
With usage:
int* p = nullptr;
bar<decltype(&foo), &foo> b(p);
MACRO can be used to remove duplication such as:
#define BAR(Func) bar<decltype(&Func), &Func>
#define TYPE_AND_VALUE(V) decltype(V), V>
to allow:
BAR(foo) b(p);
bar<TYPE_AND_VALUE(&foo)> b(p);
Upvotes: 1
Reputation: 5222
In C++11 classes cannot deduce all the types of a passed function. But a function can. So this function can be written:
template<typename Ret, typename Param>
Deduced_Types<Ret, Param> deduce_type(Ret (*)(Param))
{
return Deduced_Types<Ret, Param>();
}
This function uses a type to store the deduced types (it has to be declared before the function):
template<typename Ret, typename Param>
class Deduced_Types
{
public:
typedef Ret Return_type;
typedef Param Parameter_type;
};
Now to test it;
int f(bool)
{
return 0;
}
int main(int argc, char* argv[])
{
decltype( deduce_type(f) )::Return_type i = 0;
return i;
}
It works.
So Now to Bar:
template< class F >
class Bar
{
public:
typedef typename F::Return_type Func_Return_type;
typedef typename F::Parameter_type Fun_Param_type;
};
which has to be called:
Bar< decltype(deduce_type(f)) > b;
(this is where You can use a macro)
Works on gcc 4.8.1 : https://godbolt.org/z/1cz2pk
Edit:
Pre C++17 function could not be passed into a template.
So what is needed is that you need to pass the function pointer into the class. And a static_assert
to verify the parameters are correct:
#include <type_traits>
struct bar
{
template<typename F, typename T>
bar(F f, T t)
{
typedef decltype(deduce_type(f)) D;
static_assert(std::is_same<typename D::Parameter_type, T>::value,"parameter has different type function parameter");
f(t);
}
};
Now instead of passing the function as a template parameter, the function pointer is passed in as a parameter:
void foo(int *p) {}
int *p;
bar b(foo, p);
Only problem here is that the class has to store this pointer for future use.
Upvotes: 1
Reputation: 2177
If you can use c++17 with it's auto template parameter (like @n.m. said in the comments) you can use it as the template parameter and then the type T with type traits.
First off we need the standard type traits and also a type trait to get the argument to a unary function (your foo) that we can write like this:
#include <type_traits>
// Trait to get the argument type of a unary function
template<typename T>
struct unary_func_arg;
template<typename R, typename T>
struct unary_func_arg< R(*)(T) >
{
using type = T;
};
This will produce an error if you put anything other than a function pointer into it since the main specialization is not declared.
After this we can finally write bar as this:
template< auto F >
struct bar
{
// Assert it's a function pointer
static_assert( std::is_pointer_v<decltype(F)> );
static_assert( std::is_function_v< std::remove_pointer_t<decltype(F)> > );
// Get the parameter type
using T = typename unary_func_arg< decltype(F) >::type;
bar(T t)
{
F(t);
}
};
We have to make sure that F is a function pointer so we static assert that, then we get the type T from our type trait.
Now you can declare f and b like this:
int* p;
bar<foo> b(p);
EDIT: If you need T to be not a pointer so you can write T*, you can either make a type trait that removes 1 pointer level or modify the type trait here to as such:
// Trait to get the argument type of a unary function
template<typename T>
struct unary_func_arg_pointer;
template<typename R, typename T>
struct unary_func_arg_pointer< R(*)(T*) >
{
using type = T;
};
Now T will be just int in this example
Upvotes: 3