I wrote the smallest example I could get to explain my issue, but I'll try explaining first.
So, let's say I have a method M
, accepting a type T
, which is a class. I can:
- call
M
with a value of typeT
- call
M
with a value of typeU
, ifT
has a constructorT(U)
But it seems I can't call M
with a value of type V
, even though U(V)
exists, and T(U)
as well.
So, direct conversions work, but indirect ones don't. I could get it working by passing an initializer list instead: M({V})
.
However, I just did that through intuition, and I have no clue as to why it works with the initializer, and why it doesn't without. To me it sounds like the compiler has the ability to perform those chained conversions then, but chooses to do so only in some arbitrary cases.
With that, you'll see in the example below a use case where passed types are function types, and that gets even more confusing to me, so there's a side question: why aren't return types used to distinguish method parameters?
In other words: as an example M(function<void (string)>)
and M(function<string (string)>)
are ambiguous, but only when passing a non-void function as argument. It seems that a void function argument one can only match the first signature, while a non-void function argument can match both.
How can I make the distinction between them to be able to provide a default return value for the void function but not the non-void one?
Example:
struct FunctionConverter {
using Function = function<string (string)>;
using FunctionVoid = function<void (string)>;
Function fn;
FunctionConverter(Function fn): fn(fn) {}
FunctionConverter(FunctionVoid fn) {
this->fn = [=](string value) {
fn(value);
return "default value";
};
}
void operator()(string value) { fn(value); }
};
void call(string value, FunctionConverter fn) { fn(value); }
void print(string message) { cout << "[print] " << message << endl; }
string printAndReturn(string message) {
cout << "[printAndReturn] " << message << endl;
return message;
}
void main() {
FunctionConverter a(print);
FunctionConverter b(printAndReturn); // more than one instance of constructor "FunctionConverter::FunctionConverter" matches the argument list:C/C++(309)
call("you work", {print});
call("you don't work", print); // no suitable constructor exists to convert from "void (std::string message)" to "FunctionConverter"C/C++(415)
}
If the example structure looks a bit contrived, it's because I want it to match as close as possible my actual code, where the API design makes more sense.
Thanks in advance