Xiaofeng
Xiaofeng

Reputation: 600

How to write a template that can deduce a type use a function's argument type?

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

Answers (3)

Jarod42
Jarod42

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

Robert Andrzejuk
Robert Andrzejuk

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

Filipe Rodrigues
Filipe Rodrigues

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

Related Questions