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?

rubenvb
  • 74,642
  • 33
  • 187
  • 332
mark
  • 7,381
  • 5
  • 36
  • 61

3 Answers3

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.

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • I would have thought the parameter in a non-deduced context already. This is good to know! – Luc Danton Sep 30 '11 at 12:24
  • How come the first version wraps the entire `function` type, but the second version only wraps the template parameter? – Kerrek SB Sep 30 '11 at 17:51
  • @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` 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 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1184 – Johannes Schaub - litb Sep 30 '11 at 18:34
  • The clarified rules in C++11 are, if I understand them correctly, that they compare the untouched parts, i.e they compare `std::function` 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. – Johannes Schaub - litb Sep 30 '11 at 18:37
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 );
}
Alex B
  • 82,554
  • 44
  • 203
  • 280
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));

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • 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, std::_Placeholder<2>))(unsigned int, unsigned int)>)’ – mark Sep 30 '11 at 10:07
  • @mark: You're right, `bind` doesn't permit argument deduction, and you'd have to say `CallFunc(...)`. So... just use the explicit conversion. – Kerrek SB Sep 30 '11 at 10:14