Let's consider two functions with same names:
int func(int&)
{
return 7;
}
int func(const int&)
{
return 5;
}
Let int mutableValue = 5
be defined somewhere. Is there any possibility that template function call
in call(mutableValue, func)
would take only int func(int&)
?
I don't want to use static_cast<int(&)(int&)>
- because it's so noisy.
Naive implementation:
template<typename Arg, typename Ret>
void call(Arg& val, Ret (&fun)(Arg&))
{
fun(val);
}
works on gcc, but does not work on clang - solutions must work on both compilers.
Accepting only const Arg&
is easy. Deleting const Arg&
overload does not help.
The important difference is that when passing by const reference, no new object is created. In the function body, the parameter is effectively an alias for the object passed in. Because the reference is a const reference the function body cannot directly change the value of that object.
A const reference is actually a reference to const. A reference is inherently const, so when we say const reference, it is not a reference that can not be changed, rather it's a reference to const. Once a reference is bound to refer to an object, it can not be bound to refer to another object.
Once you have a const object, it cannot be assigned to a non-const reference or use functions that are known to be capable of changing the state of the object. This is necessary to enforce the const-ness of the object, but it means you need a way to state that a function should not make changes to an object.
I understand now that a const reference to a non const object does have a purpose: to prevent the reference from modifying the object. The non const object can still be modified by other means but not by this const reference.
I believe clang is the only compiler that gets this right. This should not compile. As long as the argument to fun
is an overload set that does not contain any function templates, template argument deduction will be attempted for each member of the overload set. If template argument deduction would succeed for more than one of these overloads, the function parameter becomes a non-deduced context [temp.deduct.call]/6.2.
In the example in question, the argument to fun
is the overload set func
, which does indeed not contain any function templates. Thus, argument deduction on fun
is attempted for both overloads of func
, which succeeds. As a result, the parameter fun
becomes a non-deduced context, which means that no argument can be deduced for Ret
and the call fails as there are no candidates (exactly what clang complains about).
To disambiguate this call, simply explicitly specify the argument for the first template parameter:
call<int>(mutableValue, func)
Since it seems to be impossible to resolve the ambiguity in one template argument deduction:
If you are ok with a change of the syntax at the call site you can separate the call into two calls/deduction passes:
template<typename Arg>
auto call(Arg& val)
{
return [&](auto (&fun)(Arg&)){ fun(val); };
}
to be called as
call(mutableValue)(func)
Another downside is however that the lambda could be stored by a caller and accidentally used later when the captured reference isn't valid anymore.
You could maybe hide this in a macro call, so that the syntax matches what you want and to reduce the potential for misuse.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With