Distinguish between function references/pointers accepting const and non-const argument with same name as function parameter
Asked Answered
O

2

7

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.

Oecology answered 24/1, 2020 at 9:44 Comment(7)
Try using template<typename Arg, typename Callable> void call(Arg& val, Callable fun) { fun(val); } insteadDocumentation
@RemyLebeau this does not help anything, because both func(int&) and func(const int&) match to Callable...Oecology
Has Ret to be deduced, or decay_t<Arg>(&fun)(Arg&) ok?Fulguration
In real use case Rets are different from Args. It's just coincidence that they are same in func case.Oecology
Can you provide it in call? (is so, you might have a solution). if it has to be deduced, I fear that you cannot resolve ambiguity.Fulguration
or there is still lambda possibility: template<typename Arg, typename Callable> void call(Arg& val, Callable fun) { fun(val); } and call(mutableValue, [](auto& arg){ return func(arg); })...Fulguration
"I don't want to use static_cast<int(&)(int&)> - because it's so noisy" You could just create a new variable/function that hides that mess for you; then the noise pretty much entirely goes away.Racine
S
4

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)
Singletary answered 24/1, 2020 at 11:22 Comment(2)
Close but not quite. For function calls, deduction is separately done on a parameter-by-parameter basis. The results are then combined at the end of the process. This means that there's no "otherwise fail" case. Both deduction trivially succeeds and that makes the parameter a non-deduced context.Ansermet
@Ansermet thanks for pointing that out. My understanding was that when that paragraph is referring to "argument deduction succeeding" it meant argument deduction for the function template as a whole succeeding, in which case a conflicting deduction from arguments to separate function parameters could make that deduction fail, which would then potentially exclude an overload from consideration. But, after rereading the sections in question, I guess you're right. I updated my answer to fix that…Singletary
S
2

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.

Staple answered 24/1, 2020 at 21:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.