Issue 20246 - isCallable fails for template opCall overload
Summary: isCallable fails for template opCall overload
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: phobos (show other issues)
Version: D2
Hardware: All Linux
: P3 normal
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2019-09-26 18:50 UTC by Paul Backus
Modified: 2024-12-01 16:35 UTC (History)
3 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Paul Backus 2019-09-26 18:50:31 UTC
import std.traits: isCallable;

struct S { int opCall(T)(T) { return 0; } }

static assert(isCallable!(S.init));

---

The above static assert should pass, but instead fails.
Comment 1 Simen Kjaeraas 2019-09-27 06:34:43 UTC
It's impossible to distinguish between the above case and this:

struct S { enum opCall(T) = true; }

Clearly, the latter is *not* callable.

To illustrate the situation perhaps more clearly:

struct S {
    template opCall(T) {
        static if (is(T == int)) {
            void opCall(T t) {}
        } else {
            enum opCall = 14;
        }
    }
}

The above is perfectly valid code, but should isCallable!S return true?
Comment 2 Paul Backus 2019-09-27 15:35:26 UTC
Seems like this is an issue with the interface, then, not the implementation; the question "is S.init callable" is undecidable, so there's not much use trying to pretend we can answer it.

One possible solution would be a trait like `isCallableWith!(S.init, int)`, that requires the user to specify the argument type(s). That way, we could just check with __traits(compiles) whether the call is valid or not.
Comment 3 Max Samukha 2019-09-28 06:27:27 UTC
(In reply to Paul Backus from comment #2)

> One possible solution would be a trait like `isCallableWith!(S.init, int)`,
> that requires the user to specify the argument type(s). That way, we could
> just check with __traits(compiles) whether the call is valid or not.

That does not take into account rvalue-ness of the arguments:

void foo(ref int) {}
store
static if (isCallableWith!(typeof(1)))
    foo(1); // error

isCallableWith would have to accept the actual arguments, (type, rvalue-ness) pairs, or both.

And if one implements isCallableWith for functions, he should also implement isInstantiatableWith for templates. And that is a case for general __traits(matchFunction/matchTemplate, symbol, args) that would return the overloads callable/instantiatable with args. isCallableWith/isInstantiatableWith could be built on that.
Comment 4 Max Samukha 2019-09-28 06:29:30 UTC
(In reply to Max Samukha from comment #3)

> isCallableWith!(typeof(1))

Typo: isCallableWith!(foo, typeof(1))
Comment 5 Paul Backus 2019-09-28 15:30:04 UTC
(In reply to Max Samukha from comment #3)
> That does not take into account rvalue-ness of the arguments:
> 
> void foo(ref int) {}
> store
> static if (isCallableWith!(typeof(1)))
>     foo(1); // error
> 
> isCallableWith would have to accept the actual arguments, (type,
> rvalue-ness) pairs, or both.

Then perhaps the best solution is to add a note to the documentation of isCallable highlighting its shortcomings, and encourage everyone to instead use __traits(compiles) directly with the actual function call they want to make.
Comment 6 Simen Kjaeraas 2020-08-10 07:26:58 UTC
*** Issue 21137 has been marked as a duplicate of this issue. ***
Comment 7 Simen Kjaeraas 2020-08-10 07:40:37 UTC
It's impossible to solve this in the general case, and even the special cases can't be solved in library code, but a __traits could check if the only member of the template is a callable (potentially also if all possible members would be callables).
Comment 8 dlangBugzillaToGithub 2024-12-01 16:35:39 UTC
THIS ISSUE HAS BEEN MOVED TO GITHUB

https://github.com/dlang/phobos/issues/9780

DO NOT COMMENT HERE ANYMORE, NOBODY WILL SEE IT, THIS ISSUE HAS BEEN MOVED TO GITHUB