C++11 Template function that takes a std::function which depends of template parameters
Asked Answered
M

3

5

I am trying to write a template function that accepts a std::function which depends on the template arguments. Unfortunately the compiler is not capable of correctly deucing the arguments to the std::function. Here some simple example code:

#include <iostream>
#include <functional>

using namespace std;

void DoSomething( unsigned ident, unsigned param )
{
    cout << "DoSomething called, ident = " << ident << ", param = "  << param << "\n";
}

template < typename Ident, typename Param >
void CallFunc( Ident ident, Param param, std::function< void ( Ident, Param ) > op )
{
    op( ident, param );
}

int main()
{
    unsigned id(1);
    unsigned param(1);

    // The following fails to compile
    // CallFunc( id, param, DoSomething );

    // this is ok 
    std::function< void ( unsigned, unsigned ) > func( DoSomething );
    CallFunc( id, param, func ); 

    return 0;
}

If I call the template with the following:

CallFunc( id, param, DoSomething );

I get the following errors:

function-tpl.cpp:25: error: no matching function for call to CallFunc(unsigned int&, unsigned int&, void (&)(unsigned int, unsigned int))

If I explicitly create a std::function of the correct type (or cast it) the problem goes away:

std::function< void ( unsigned, unsigned ) > func( DoSomething );
CallFunc( id, param, func );

How would I code this so that the explicit temporary is not needed?

Morose answered 30/9, 2011 at 9:57 Comment(0)
L
2

If you are using templates, you can avoid std::function entirely, unless for some reason you want to specifically restrict the function to take std::function:

template < typename Ident, typename Param, typename Func >
void CallFunc( Ident ident, Param param, Func op )
{
    op( ident, param );
}
Lingenfelter answered 30/9, 2011 at 10:13 Comment(1)
Did you forget to remove the comment characters "//" before the offending line? I have GCC4.6.1 and that GCC rejects said line.Tripartition
T
8

You need to make the third function parameter a non-deduced context for the template parameters therein. Then the compiler will not compare the argument type against the parameter type without also considering all implicit conversions (the Standard says, and C++0x clarified this further, that for a function parameter where there are no template parameters in deducing positions, all implicit conversions are allowed in bridging a difference).

template < typename T > struct id { typedef T type; };

template < typename Ident, typename Param >
void CallFunc( Ident ident, Param param, 
               typename id<std::function< void ( Ident, Param ) >>::type op )
{
    op( ident, param );
}

Instead of id you can use boost::identity. In C++0x and compilers that support it, you can have a more readable edition using alias templates

template < typename T > using nondeduced = typename id<T>::type;

Then your code becomes simply

template < typename Ident, typename Param >
void CallFunc( Ident ident, Param param, 
               std::function< nondeduced<void ( Ident, Param )> > op )
{
    op( ident, param );
}

However GCC does not yet support alias templates.

Tripartition answered 30/9, 2011 at 11:42 Comment(4)
I would have thought the parameter in a non-deduced context already. This is good to know!Pharyngology
How come the first version wraps the entire function type, but the second version only wraps the template parameter?Chrystalchryste
@KerrekSB because of different interpretations in compilers of the wording in C++03, I came to wrap the entire parameter type into a non-deduced context. Some compilers would pick the toplevel parts of the type before entering the non-deduced context (i.e the std::function<void(..., ...)> parts) and compare that against arguments, triggering a deduction failure. Other compilers wouldn't do that. If wrapping around the whole type, no "untouched" parts are left, so it's all ... (black hole), and no mismatches can occur. See open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1184Tripartition
The clarified rules in C++11 are, if I understand them correctly, that they compare the untouched parts, i.e they compare std::function<void(..., ...)> against FunctionType *, and find a mismatch. But upon finding a mismatch, it will allow all implicit conversions to bridge this, if the type contains only template parameters in non-deduced contexts.Tripartition
L
2

If you are using templates, you can avoid std::function entirely, unless for some reason you want to specifically restrict the function to take std::function:

template < typename Ident, typename Param, typename Func >
void CallFunc( Ident ident, Param param, Func op )
{
    op( ident, param );
}
Lingenfelter answered 30/9, 2011 at 10:13 Comment(1)
Did you forget to remove the comment characters "//" before the offending line? I have GCC4.6.1 and that GCC rejects said line.Tripartition
C
0

You can do the conversion inline or use bind. Neither is particularly pretty, but they get the job done:

CallFunc(id, param, std::function<void(unsigned, unsigned)>(DoSomething));

CallFunc(id, param, std::bind(DoSomething, std::placeholders::_1, std::placeholders::_2));

Chrystalchryste answered 30/9, 2011 at 10:0 Comment(2)
The std::bind produces the following error: function-tpl.cpp:37: error: no matching function for call to ‘CallFunc(unsigned int&, unsigned int&, std::_Bind<void (*(std::_Placeholder<1>, std::_Placeholder<2>))(unsigned int, unsigned int)>)’Morose
@mark: You're right, bind doesn't permit argument deduction, and you'd have to say CallFunc<unsigned, unsigned>(...). So... just use the explicit conversion.Chrystalchryste

© 2022 - 2024 — McMap. All rights reserved.