Following up my question c - How do implicit conversions work when using an intermediate type?, from which I understood the rule of 1 implicit conversion max, I'm trying to understand a more advanced use case involving function arguments.
Let's say I have a function, which accepts as a parameter another function. That function parameter could either return something, or return nothing (void). Therefore, I want to overload the function definition, to accept in one case the non-void function argument, and in the other case the void function argument.
using VoidType = void (string);
using NonVoidType = string (string);
string call(VoidType *fn, string arg) {
cout << "[using void function]" << endl;
fn(arg);
return arg;
}
string call(NonVoidType *fn, string arg) {
cout << "[using non void function]" << endl;
return fn(arg);
}
When calling the function, the given argument has a known type, so the overload selection should be straightforward, such as here:
void printVoid(string message) {
cout << message << endl;
}
string printNonVoid(string message) {
cout << message << endl;
return message;
}
void test() {
call(printVoid, "call(printVoid)");
call(printNonVoid, "call(printNonVoid)");
}
But it's not when I have intermediate types, such as std::function
wrappers:
string call(function<VoidType> fn, string arg) {
cout << "[using void function]" << endl;
fn(arg);
return arg;
}
string call(function<NonVoidType> fn, string arg) {
cout << "[using non void function]" << endl;
return fn(arg);
}
void test() {
call(printVoid, "call(printVoid)");
call(printNonVoid, "call(printNonVoid)"); // more than one instance of overloaded function "call" matches the argument list:C/C (308)
}
for which I get the error more than one instance of overloaded function "call" matches the argument list:C/C (308)
on the second call.
A solution to this is to instantiate the std::function
explicitly before calling:
void test() {
call(function(printNonVoid), "call(function(printNonVoid))");
}
But, how is call(function(printNonVoid), "xxx")
so different from call(printNonVoid, "xxx")
knowing that in the latter case I would expect the implicit conversion to do the equivalent to what is done in the former case.
Am I missing something, like some intermediate, hidden copy constructors or whatever?
Full example:
#include <iostream>
using namespace std;
using VoidType = void (string);
using NonVoidType = string (string);
string callFromPointer(VoidType *fn, string arg) {
cout << "[callFromPointer] void" << endl;
fn(arg);
return "<void>";
}
string callFromPointer(NonVoidType *fn, string arg) {
cout << "[callFromPointer] non void" << endl;
return fn(arg);
}
string callFromFunction(function<VoidType> fn, string arg) {
cout << "[callFromFunction] void" << endl;
fn(arg);
return "<void>";
}
string callFromFunction(function<NonVoidType> fn, string arg) {
cout << "[callFromFunction] non void" << endl;
return fn(arg);
}
void printVoid(string message) { cout << "\t[printVoid] " << message << endl; }
string printNonVoid(string message) {
cout << "\t[printNonVoid] " << message << endl;
return message;
}
void sep() { cout << "\n----\n" << endl; }
void main() {
callFromPointer(printVoid, "callFromPointer(printVoid)");
callFromPointer(printNonVoid, "callFromPointer(printNonVoid)");
sep();
callFromFunction(printVoid, "callFromFunction(printVoid)");
callFromFunction(printNonVoid, "callFromFunction(printNonVoid)"); // more than one instance of overloaded function "callFromFunction" matches the argument list:C/C (308)
sep();
callFromFunction(function(printVoid), "callFromFunction(function(printVoid))");
callFromFunction(function(printNonVoid), "callFromFunction(function(printNonVoid))");
}
CodePudding user response:
This is because std::string(*)(std::string)
is "compatible" with both std::function<void(std::string)>
and std::function<std::string(std::string)>
since the former can simply ignore the underlying function's return value.
That is, both of the following are valid:
std::function<void(std::string)> voidFunc(printNonVoid);
std::function<std::string(std::string)> strFunc(printNonVoid);
Since neither is a "better" match, overload resolution fails when calling callFromFunction(printNonVoid, "...")
.
The reason it works when using std::function(printNonVoid)
is because class template argument deduction takes over, and there is a deduction guide for T(*)(Args...) -> std::function<T(Args...)>
. That deduction guide turns std::function(printNonVoid)
into std::function<std::string(std::string)>(printNonVoid)
. Since that's an exact match with no conversion necessary for the std::function<std::string(std::string)>
overload of callFromFunction
that overload is chose by overload resolution.
That deduction guide doesn't come into play when performing overload resolution with just the raw function name though, since the function arguments have to explicitly declare std::function
's template argument.