www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Difference between "can call" and "can compile"

reply Steven Schveighoffer <schveiguy gmail.com> writes:
Consider a template function like this:

void foo(T)(T v)
{
    auto x = v.someProperty;
}

Now, I might create a constraint based on whether I can compile that 
function:

void bar(alias x)() if (__traits(compiles, x(1)))
{
    x(1);
}

now, if I try to call bar like:

bar!foo();

I get a compilation error. But it's not in foo, it's in bar. It says:

Error: template instance onlineapp.bar!(foo) does not match template 
declaration bar(alias x)()
   with x = foo(T)(T v)
   must satisfy the following constraint:
        __traits(compiles, x(1))

In this instance, the error message is straightforward. With a simple 
case like this, I can figure it out and move on.

However, in a more complex case, perhaps the constraint is encapsulated 
in a template. Perhaps that template basically checks if it can call it 
in various ways, with various types. Perhaps it depends on the context 
in which it was called.

And my alias I'm passing in is INTENDED to work. In fact, it might be 
specifically DESIGNED to work with this exact function. However, I don't 
get to see why it doesn't. I just get a "must satisfy the following 
constraint: isSomeConstraint!foo"

The problem is, if the intention is for it to pass, but the 
implementation is bugged, the error message is useless. If the thing I'm 
passing is a type with 200 LOC, and there's an error buried in there 
somewhere, the compiler is telling me "somewhere in this, it doesn't 
work". And sometimes those function signatures and their constraints are 
hairy themselves.

My usual steps taken at this point are to extract the constraint 
template and actually try to call the functions in the constraints with 
that type, and see what fails. This is somewhat annoying, and sometimes 
difficult to do based on where the code that implements the constraint 
actually is, and where the code actually calling it is (I may have to 
duplicate a lot of stuff).

But the thing is -- we already HAVE a constraint system that says 
whether the code should be callable or not -- the template constraint! 
In fact, we can declare right on foo that it should only compile if I 
can call someProperty on the value:

void foo(T)(T v) if (__traits(compiles, v.someProperty))
{
    auto x = v.someProperty;
}

However, for this extra bit of forethought, I get no different error 
messages.

I was wondering, would it be a useful addition to have a way to say "can 
call" instead of "can compile"? In other words, this has a valid 
template IFTI match, regardless of whether it compiles or not. In which 
case, you can distinguish mismatch errors from implementation errors.

In the example above, foo without the template constraint instantiated 
with int has a matched call, but the match doesn't compile. Whereas, the 
one with the template constraint doesn't have a match, and so the call 
itself will not compile. This difference could push the error down to 
where it really belongs, and save me the time of having to perform 
exploratory surgery on a library and its dependencies.

Thoughts?

-Steve
Sep 07 2020
next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer 
wrote:
 I was wondering, would it be a useful addition to have a way to 
 say "can call" instead of "can compile"? In other words, this 
 has a valid template IFTI match, regardless of whether it 
 compiles or not. In which case, you can distinguish mismatch 
 errors from implementation errors.

 Thoughts?

 -Steve
Yes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
Sep 07 2020
parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Monday, 7 September 2020 at 15:08:42 UTC, FeepingCreature 
wrote:
 On Monday, 7 September 2020 at 14:57:24 UTC, Steven 
 Schveighoffer wrote:
 I was wondering, would it be a useful addition to have a way 
 to say "can call" instead of "can compile"? In other words, 
 this has a valid template IFTI match, regardless of whether it 
 compiles or not. In which case, you can distinguish mismatch 
 errors from implementation errors.

 Thoughts?

 -Steve
Yes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
I can see the appeal of the feature. Let me think about how difficult that would be to implement.
Sep 07 2020
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 7 September 2020 at 15:24:23 UTC, Stefan Koch wrote:
 On Monday, 7 September 2020 at 15:08:42 UTC, FeepingCreature 
 wrote:
 On Monday, 7 September 2020 at 14:57:24 UTC, Steven 
 Schveighoffer wrote:
 I was wondering, would it be a useful addition to have a way 
 to say "can call" instead of "can compile"? In other words, 
 this has a valid template IFTI match, regardless of whether 
 it compiles or not. In which case, you can distinguish 
 mismatch errors from implementation errors.

 Thoughts?

 -Steve
Yes yes yes yes yes *yes yes*! Please!!!!! This would make template errors so unbelievably much better.
I can see the appeal of the feature. Let me think about how difficult that would be to implement.
That would be so, so awesome. Let me clarify how important this is to me: I actually had a section at the end of my proposed DConf Online talk where I was gonna beg the devs to add this exact feature. In cases where you want a template function to match, but *maybe* will be fine if it doesn't match, so you have to keep going even if it doesn't __traits(compiles), this can be the difference between half an hour of debugging and instantly being told the (usually trivial) problem. To explain: You can pass `serialized`'s decode functions a helper function that can be used to decode problematic leaf types like `SysTime` or UUID in a protocol-specific fashion. However, if that helper doesn't match, we still want to keep going and try our default decode logic. So how do we check if a helper matches? For instance, the helper may be SysTime decode(T : SysTime)(const string attribute) { return SysTime.fromISOExtString(attribute); } And then internally I do static if (__traits(compiles, decode!SysTime)). But say that I forgot to import std.datetime. Then __traits(compiles) will return false and I will have no idea what happened. What I actually *want* is __traits(canInstantiate, decode, SysTime) - check if the template constraints can be satisfied enough to find an unambiguous template match. If I then try to instantiate the template and the compiler raises an error, that's okay! That's an error I want. I want that error. Give me that error. Please. :) (Note that this doesn't *exactly* reduce to a function call. An important part here is that I don't know what the function will be called with; I have to instantiate the function template so that I can check what parameters it requires, which I'll then recursively try to decode.)
Sep 07 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/7/20 11:48 AM, FeepingCreature wrote:
 But say that I forgot to import std.datetime. Then __traits(compiles) 
 will return false and I will have no idea what happened. What I actually 
 *want* is __traits(canInstantiate, decode, SysTime) - check if the 
 template constraints can be satisfied enough to find an unambiguous 
 template match. If I then try to instantiate the template and the 
 compiler raises an error, that's okay! That's an error I want. I want 
 that error. Give me that error. Please. :)
 
 (Note that this doesn't *exactly* reduce to a function call. An 
 important part here is that I don't know what the function will be 
 called with; I have to instantiate the function template so that I can 
 check what parameters it requires, which I'll then recursively try to 
 decode.)
This is somewhat different than what I was proposing. However, it's very closely related. I absolutely need it to be matched with a function call, I don't want to have to do type acrobatics or check for UFCS, or whatever. However, I think both our needs are satisfied if the feature is something like checking to see that all symbols are resolved and that all instantiations are unambiguous. What happens after that should be considered a normal ungagged error. If I can pull out a non-template analogy, imagine you had code like: int foo() { blahblah(); // unresolved symbol } int bar() { return foo(); } And instead of the error on the "blahblah" line, you got an error that foo doesn't exist, and so bar cannot be compiled. This is kind of how the current template state of affairs is. I want the same benefits for template code that non-template code has. -Steve
Sep 07 2020
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Monday, 7 September 2020 at 16:10:13 UTC, Steven Schveighoffer 
wrote:
 This is somewhat different than what I was proposing. However, 
 it's very closely related.

 I absolutely need it to be matched with a function call, I 
 don't want to have to do type acrobatics or check for UFCS, or 
 whatever.

 However, I think both our needs are satisfied if the feature is 
 something like checking to see that all symbols are resolved 
 and that all instantiations are unambiguous. What happens after 
 that should be considered a normal ungagged error.

 If I can pull out a non-template analogy, imagine you had code 
 like:

 int foo()
 {
    blahblah(); // unresolved symbol
 }

 int bar()
 {
    return foo();
 }

 And instead of the error on the "blahblah" line, you got an 
 error that foo doesn't exist, and so bar cannot be compiled. 
 This is kind of how the current template state of affairs is. I 
 want the same benefits for template code that non-template code 
 has.

 -Steve
Yesss. Okay, so as far as I can see there's two avenues that should give us both what we want. The first, and IMO best one (but W&A won't agree) is a huge breaking language change that errors in templates that have matched the constraints are always reported as errors, even in a gagged context. The second, more limited one that I think also gives you what you want (?) is a pragma *like* __traits(compiles), maybe __traits(instantiates), with the change that instead of gagging everything, it captures only one category of error - "the call couldn't find a matching function or template" - and only from the expression directly passed to it. All other errors are reported as usual. Would that do it?
Sep 07 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/8/20 1:03 AM, FeepingCreature wrote:
 On Monday, 7 September 2020 at 16:10:13 UTC, Steven Schveighoffer wrote:
 This is somewhat different than what I was proposing. However, it's 
 very closely related.

 I absolutely need it to be matched with a function call, I don't want 
 to have to do type acrobatics or check for UFCS, or whatever.

 However, I think both our needs are satisfied if the feature is 
 something like checking to see that all symbols are resolved and that 
 all instantiations are unambiguous. What happens after that should be 
 considered a normal ungagged error.

 If I can pull out a non-template analogy, imagine you had code like:

 int foo()
 {
    blahblah(); // unresolved symbol
 }

 int bar()
 {
    return foo();
 }

 And instead of the error on the "blahblah" line, you got an error that 
 foo doesn't exist, and so bar cannot be compiled. This is kind of how 
 the current template state of affairs is. I want the same benefits for 
 template code that non-template code has.
Yesss. Okay, so as far as I can see there's two avenues that should give us both what we want. The first, and IMO best one (but W&A won't agree) is a huge breaking language change that errors in templates that have matched the constraints are always reported as errors, even in a gagged context.
No, the result of __traits(compiles) or is(typeof(...)) cannot be changed. I would never want that. What we would need is a new feature that then we can migrate existing code to use.
 The second, more limited one that I think also gives you what 
 you want (?) is a pragma *like* __traits(compiles), maybe 
 __traits(instantiates), with the change that instead of gagging 
 everything, it captures only one category of error - "the call couldn't 
 find a matching function or template" - and only from the expression 
 directly passed to it. All other errors are reported as usual.
 
 Would that do it?
Yes, this is exactly what I'm looking for. I was going to call it __traits(canCall) or something, but instantiates makes perfect sense, since this only really is important for template instantiation. In order to facilitate more complex conditions, I would say "the code in the expression passes semantic, with all template instantiations in the checked expression are matched" as the description. For actual errors in the code being checked, it should return false just like __traits(compiles). e.g.: void foo()() { import non.existent.mod; } __traits(compiles, foo()); // false __traits(instantiates, foo()); // true, foo's body isn't checked for errors, just that it instantiates. __traits(compiles, () {import non.existent.mod;} ()); // false __traits(instantiates, () {import non.existent.mod;} ()); // false, the code inside the expression doesn't pass semantic -Steve
Sep 08 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven 
Schveighoffer wrote:
 What we would need is a new feature that then we can migrate 
 existing code to use.
On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.
 The second, more limited one that I think also gives you what 
 you want (?) is a pragma *like* __traits(compiles), maybe 
 __traits(instantiates), with the change that instead of 
 gagging everything, it captures only one category of error - 
 "the call couldn't find a matching function or template" - and 
 only from the expression directly passed to it. All other 
 errors are reported as usual.
 
 Would that do it?
Yes, this is exactly what I'm looking for. I was going to call it __traits(canCall) or something, but instantiates makes perfect sense, since this only really is important for template instantiation.
If we're going to take this approach, why stop here? Surely there are other categories of error that a programmer might want to query: __traits(parses), __traits(resolvesIdentifiers), __traits(typeChecks), __traits(safetyChecks), __traits(passesCtorFlowAnalysis), etc. Might as well go ahead and add them all. The problem described in the OP of this thread is that the compiler fails to give the programmer useful information in certain situations. The solution is to improve the compiler so that it gives better information, not to add unprincipled hacks to the language so that the programmer can work around the compiler's deficiencies by hand.
Sep 08 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/8/20 1:06 PM, Paul Backus wrote:
 On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven Schveighoffer wrote:
 What we would need is a new feature that then we can migrate existing 
 code to use.
On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.
The error could cause subtle differences. The code could actually COMPILE, but not take the path intended. In which case the error is useless, even if it's more verbose. But I would be able to live with better error messages for the current situation. And by better error messages, I mean show me where my code failed to compile, not give me 5 pages of speculative compilation errors up a giant tree of calls.
 
 If we're going to take this approach, why stop here? Surely there are 
 other categories of error that a programmer might want to query: 
 __traits(parses), __traits(resolvesIdentifiers), __traits(typeChecks), 
 __traits(safetyChecks), __traits(passesCtorFlowAnalysis), etc. Might as 
 well go ahead and add them all.
I'm looking to duplicate the error system for functions. That is, here is a function signature, don't worry about the innards, if you can call it with that signature, then you are done with the check. Then try to compile what you think should be valid, and if there's an error, just report that error. I don't want any of those other features. I want the equivalent of "I only looked at the documentation, and called the function as specified."
 The problem described in the OP of this thread is that the compiler 
 fails to give the programmer useful information in certain situations. 
 The solution is to improve the compiler so that it gives better 
 information
If you can solve that problem, then I'd be all for that.
 not to add unprincipled hacks to the language so that the 
 programmer can work around the compiler's deficiencies by hand.
This would be the opposite of working around things by hand. I already do that today. -Steve
Sep 08 2020
parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 8 September 2020 at 17:49:08 UTC, Steven 
Schveighoffer wrote:
 On 9/8/20 1:06 PM, Paul Backus wrote:
 On Tuesday, 8 September 2020 at 13:57:34 UTC, Steven 
 Schveighoffer wrote:
 What we would need is a new feature that then we can migrate 
 existing code to use.
On the other hand, improving the compiler to give more informative output about existing errors in existing code requires no migration and solves this entire category of problem, rather than just the subset involving IFTI or template argument deduction.
The error could cause subtle differences. The code could actually COMPILE, but not take the path intended. In which case the error is useless, even if it's more verbose.
I guess I've failed to communicate clearly. When I talk about "errors" here, unless I specifically say otherwise, I am referring exclusively to "top-level" non-speculative errors--the kind that DMD prints with red letters and that cause compilation to fail. By definition, any code that contains such an error cannot compile, so the hypothetical you raise here is impossible.
 And by better error messages, I mean show me where my code 
 failed to compile, not give me 5 pages of speculative 
 compilation errors up a giant tree of calls.
Well, the compiler is already showing you where your code failed to compile: it failed in the template constraint, because `__traits(compiles, whatever)` evaluated to false. :) What you and I really want is for the compiler to show us the *root cause* of that failure (which `-verrors=spec` currently succeeds at), and to do so without overwhelming us with irrelevant information (which `-verrors=spec` currently fails at).
 The problem described in the OP of this thread is that the 
 compiler fails to give the programmer useful information in 
 certain situations. The solution is to improve the compiler so 
 that it gives better information
If you can solve that problem, then I'd be all for that.
If it's possible to selectively un-gag errors based on whether they happened during template argument deduction, it should also be possible to selectively un-gag errors based on whether they happened during template instantiation in general.
Sep 08 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/8/20 2:24 PM, Paul Backus wrote:
 On Tuesday, 8 September 2020 at 17:49:08 UTC, Steven Schveighoffer wrote:
 The error could cause subtle differences. The code could actually 
 COMPILE, but not take the path intended. In which case the error is 
 useless, even if it's more verbose.
I guess I've failed to communicate clearly. When I talk about "errors" here, unless I specifically say otherwise, I am referring exclusively to "top-level" non-speculative errors--the kind that DMD prints with red letters and that cause compilation to fail. By definition, any code that contains such an error cannot compile, so the hypothetical you raise here is impossible.
Like for instance: struct S { import std.range : isOutputRange; void toString(Output)(Output output) if (isOutputRange!(Output, dchar)) { put(output, "hello, this is an S!"); // no import of std.range.put } } Now, writeln(S.init) => S() Wait, where's my specified message? I've literally spent hours on stuff like this, where I'm racking my brain trying to figure out why that function can't be called, only to realize that the function doesn't compile in the first place, when called with the specified constraints satisfied. Whereas if writeln checked using the proposed __traits(instantiates) instead of __traits(compiles) on the call to S.toString(lockedTextOutput) (or whatever it does), then instead of the wrong path, I get a compilation error, telling me that my toString doesn't compile. It's also possible that a compilation error happens in a path that isn't chosen because __traits(compiles) returns false in the path we expect, but the path taken instead doesn't have a __traits(compiles) guarding it. In which case, a weirder unexpected error message occurs far away from the true problem.
 Well, the compiler is already showing you where your code failed to 
 compile: it failed in the template constraint, because 
 `__traits(compiles, whatever)` evaluated to false. :)
In simple cases, this is enough. In complex cases, the error can be very far away from where the problem is.
 What you and I really want is for the compiler to show us the *root 
 cause* of that failure (which `-verrors=spec` currently succeeds at), 
 and to do so without overwhelming us with irrelevant information (which 
 `-verrors=spec` currently fails at).
I essentially want to know why it made the decisions it made. So yes, in some cases, this might involve seeing ALL the speculative compilation output, and sifting through it. In the majority of cases, I want to know why it couldn't call my function foo()(int) with an int parameter, which is what it's telling me. This would not solve all template compilation problems. Just most of them.
 If you can solve that problem, then I'd be all for that.
If it's possible to selectively un-gag errors based on whether they happened during template argument deduction, it should also be possible to selectively un-gag errors based on whether they happened during template instantiation in general.
The point is not to ungag errors during argument deduction or gag them afterwards, but to stop trying to compile after deduction is done for that call. Essentially, don't actually instantiate the template, just see whether there is a matching template you would instantate. -Steve
Sep 08 2020
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven 
Schveighoffer wrote:
 Now,

 writeln(S.init) => S()

 Wait, where's my specified message? I've literally spent hours 
 on stuff like this, where I'm racking my brain trying to figure 
 out why that function can't be called, only to realize that the 
 function doesn't compile in the first place, when called with 
 the specified constraints satisfied.

 Whereas if writeln checked using the proposed 
 __traits(instantiates) instead of __traits(compiles) on the 
 call to S.toString(lockedTextOutput) (or whatever it does), 
 then instead of the wrong path, I get a compilation error, 
 telling me that my toString doesn't compile.
writeln could just as easily use __traits(hasMember) to check for the presence of toString, and print a warning message if it exists but doesn't compile. In other words, the problem here is that writeln assumes "toString doesn't compile" implies "toString isn't supposed to compile," which in most cases is not actually true. Adding a new language feature is neither necessary nor sufficient to correct that assumption. I agree that it would probably be a good idea to change writeln (and other similar code) so that it does not make these kinds of assumptions.
 The point is not to ungag errors during argument deduction or 
 gag them afterwards, but to stop trying to compile after 
 deduction is done for that call. Essentially, don't actually 
 instantiate the template, just see whether there is a matching 
 template you would instantate.
From the programmer's perspective, is there any difference? Let me make this concrete. Here's a contrived example program that illustrates the problem from your first post in this thread: --- example.d void use(alias fun)() if (__traits(compiles, fun())) {} void bad()() { oops(); } void irrelevant()() { oops(); } void test() { enum _ = __traits(compiles, irrelevant()); use!bad(); } --- Here's what the compiler currently prints when you compile it: --- example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- Here's what the compiler currently prints when you compile it with `-verrors=spec`: --- (spec:1) example.d(12): Error: undefined identifier `oops` (spec:1) example.d(17): Error: template instance `example.irrelevant!()` error instantiating (spec:1) example.d(17): Error: template instance `example.irrelevant!()` cannot resolve forward reference (spec:1) example.d(17): Error: template `example.irrelevant` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(10): `irrelevant()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- And here's what I would like the compiler to print with my proposed `-verrors=spec-but-only-stuff-i-care-about` flag: --- (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` (spec:1) example.d(7): Error: undefined identifier `oops` (spec:1) example.d(2): Error: template instance `example.bad!()` error instantiating (spec:1) example.d(2): Error: template instance `example.bad!()` cannot resolve forward reference (spec:1) example.d(2): Error: template `example.bad` cannot deduce function from argument types `!()()`, candidates are: (spec:1) example.d(5): `bad()()` example.d(18): Error: template instance `example.use!(bad)` does not match template declaration `use(alias fun)()` with `fun = bad()()` must satisfy the following constraint: ` __traits(compiles, fun())` --- Notice how the above includes messages about speculative errors in `bad`, but does NOT include messages about speculative errors in `irrelevant`.
Sep 08 2020
next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Tue, Sep 08, 2020 at 07:48:36PM +0000, Paul Backus via Digitalmars-d wrote:
[...]
 writeln could just as easily use __traits(hasMember) to check for the
 presence of toString, and print a warning message if it exists but
 doesn't compile. In other words, the problem here is that writeln
 assumes "toString doesn't compile" implies "toString isn't supposed to
 compile," which in most cases is not actually true. Adding a new
 language feature is neither necessary nor sufficient to correct that
 assumption.
 
 I agree that it would probably be a good idea to change writeln (and
 other similar code) so that it does not make these kinds of
 assumptions.
[...] +1. I think in the past, before the we had __traits(hasMember), __traits(compiles) was used as a kind of poor man's substitute, based on the shaky assumption that "it compiles" == "it has member X". Now that we have better ways of expressing this, we should get rid of __traits(compiles) or other similar hacks based on similar shaky assumptions. T -- Life begins when you can spend your spare time programming instead of watching television. -- Cal Keegan
Sep 08 2020
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/8/20 3:48 PM, Paul Backus wrote:

 
 writeln could just as easily use __traits(hasMember) to check for the 
 presence of toString, and print a warning message if it exists but 
 doesn't compile. In other words, the problem here is that writeln 
 assumes "toString doesn't compile" implies "toString isn't supposed to 
 compile," which in most cases is not actually true. Adding a new 
 language feature is neither necessary nor sufficient to correct that 
 assumption.
hasMember is something I've been using a lot more lately. But it's not the same. For instance, if the member was toString(int x), then it has the member toString, but would not match a call, and therefore should not be used. Contrived, but possible in other contexts. Or the "member" could be a UFCS function, where __traits(hasMember) doesn't work.
 I agree that it would probably be a good idea to change writeln (and 
 other similar code) so that it does not make these kinds of assumptions.
possibly with writeln, you can make the assumption that toString really should be picked even if it's not formed correctly. But that's not the case for other things.
 Let me make this concrete. Here's a contrived example program that 
 illustrates the problem from your first post in this thread:
 
 --- example.d
 void use(alias fun)()
      if (__traits(compiles, fun()))
 {}
 
 void bad()()
 {
      oops();
 }
 
 void irrelevant()()
 {
      oops();
 }
 
 void test()
 {
      enum _ = __traits(compiles, irrelevant());
      use!bad();
 }
 ---
 
...
 And here's what I would like the compiler to print with my proposed 
 `-verrors=spec-but-only-stuff-i-care-about` flag:
 
 ---
 (spec:1) example.d(7): Error: undefined identifier `oops`
 (spec:1) example.d(2): Error: template instance `example.bad!()` error 
 instantiating
 (spec:1) example.d(2): Error: template instance `example.bad!()` cannot 
 resolve forward reference
 (spec:1) example.d(2): Error: template `example.bad` cannot deduce 
 function from argument types `!()()`, candidates are:
 (spec:1) example.d(5):        `bad()()`
 (spec:1) example.d(7): Error: undefined identifier `oops`
 (spec:1) example.d(2): Error: template instance `example.bad!()` error 
 instantiating
 (spec:1) example.d(2): Error: template instance `example.bad!()` cannot 
 resolve forward reference
 (spec:1) example.d(2): Error: template `example.bad` cannot deduce 
 function from argument types `!()()`, candidates are:
 (spec:1) example.d(5):        `bad()()`
 example.d(18): Error: template instance `example.use!(bad)` does not 
 match template declaration `use(alias fun)()`
    with `fun = bad()()`
    must satisfy the following constraint:
 `       __traits(compiles, fun())`
 ---
 
 Notice how the above includes messages about speculative errors in 
 `bad`, but does NOT include messages about speculative errors in 
 `irrelevant`.
Here's what it says when there is no constraint (and when I actually call fun inside use): onlineapp.d(9): Error: undefined identifier oops onlineapp.d(4): Error: template instance onlineapp.bad!() error instantiating onlineapp.d(20): instantiated from here: use!(bad) This is what I want it to do. Why can't it just do that? Why do I need to see 10 more or even 5 more lines of irrelevant error messages? -Steve
Sep 08 2020
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven 
Schveighoffer wrote:
 Here's what it says when there is no constraint (and when I 
 actually call fun inside use):

 onlineapp.d(9): Error: undefined identifier oops
 onlineapp.d(4): Error: template instance onlineapp.bad!() error 
 instantiating
 onlineapp.d(20):        instantiated from here: use!(bad)

 This is what I want it to do. Why can't it just do that? Why do 
 I need to see 10 more or even 5 more lines of irrelevant error 
 messages?

 -Steve
Hear, hear. And yes, `hasMember` also doesn't solve my problem in any way at all.
Sep 14 2020
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven 
Schveighoffer wrote:
 Here's what it says when there is no constraint (and when I 
 actually call fun inside use):

 onlineapp.d(9): Error: undefined identifier oops
 onlineapp.d(4): Error: template instance onlineapp.bad!() error 
 instantiating
 onlineapp.d(20):        instantiated from here: use!(bad)

 This is what I want it to do. Why can't it just do that? Why do 
 I need to see 10 more or even 5 more lines of irrelevant error 
 messages?
It sounds to me like your problem is that template constraints are doing the exact thing they're designed to do: causing errors that would otherwise be reported inside a template to be reported outside of it. Re: irrelevant error messages, I think they're unavoidable in the general case. What if there are multiple template overloads? Only one of them is going to have the error you care about, but the compiler has no way to know which one. And likewise with static if and static assert statements inside the template body. The optimal solution to this problem, from a programming-language-theory perspective, is a system like Rust's traits that allows the compiler to type-check generic code prior to instantiation. Since we don't have that, we're going to have to settle for something "good enough."
Sep 15 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/15/20 7:26 AM, Paul Backus wrote:
 On Tuesday, 8 September 2020 at 20:04:32 UTC, Steven Schveighoffer wrote:
 Here's what it says when there is no constraint (and when I actually 
 call fun inside use):

 onlineapp.d(9): Error: undefined identifier oops
 onlineapp.d(4): Error: template instance onlineapp.bad!() error 
 instantiating
 onlineapp.d(20):        instantiated from here: use!(bad)

 This is what I want it to do. Why can't it just do that? Why do I need 
 to see 10 more or even 5 more lines of irrelevant error messages?
It sounds to me like your problem is that template constraints are doing the exact thing they're designed to do: causing errors that would otherwise be reported inside a template to be reported outside of it.
That's not what they are designed to do. It does not cause an error if a constraint doesn't pass, it's just not a match. Template constraints are designed to allow the template author to fine-tune how his template can be used. This allows template overloading that otherwise would be impossible or at least really difficult.
 Re: irrelevant error messages, I think they're unavoidable in the 
 general case. What if there are multiple template overloads? Only one of 
 them is going to have the error you care about, but the compiler has no 
 way to know which one. And likewise with static if and static assert 
 statements inside the template body.
It would know which one -- the one that matches. I want to say with my template constraint "I found a match that I can call, so use me". and then when the match fails to compile (NOT fails to match), then I get a targeted error message.
 The optimal solution to this problem, from a programming-language-theory 
 perspective, is a system like Rust's traits that allows the compiler to 
 type-check generic code prior to instantiation. Since we don't have 
 that, we're going to have to settle for something "good enough."
The compiler can type-check the interface, without checking the code. We can already do this with types, I want to do it with calls. An example void foo(T)(T t) if (isIntegral!T) { int x = t; } foo has declared "I accept all types that are integral", even though foo doesn't actually work for integers larger than int (a bug in either the constraints or the offending line). But if you do foo(long.max), it does not come back and say "can't find foo", it says "error, can't assign int x to long t", or whatever. That's the error I want to see. Now I can either change the constraint to reflect it should only take int size or less, or change the line to work with longs. In contrast, there's no way in the template constraints to say "I accept all parameters for which I can call it this way". You have to say "I accept all parameters for which this code compiles". There is a difference in specification, and I would prefer the latter. Without this facility, you are hiding all implementation errors, regardless of origin, behind constraints, which makes it really difficult to find them. -Steve
Sep 15 2020
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven 
Schveighoffer wrote:
    void toString(Output)(Output output) if 
 (isOutputRange!(Output, dchar))
    {
       put(output, "hello, this is an S!"); // no import of 
 std.range.put
    }
...
 if writeln checked using the proposed __traits(instantiates) 
 instead of __traits(compiles) on the call to 
 S.toString(lockedTextOutput) (or whatever it does), then 
 instead of the wrong path, I get a compilation error, telling 
 me that my toString doesn't compile.
In theory, `is(typeof(S.init.toString(suitableOutputRange)))` could be true even if there is a semantic error in the body of your S.toString method. The typeof expression only needs to resolve the method then produce the type of that method. You specified the return type as `void` (not `auto`), so it is overkill to check the semantics of the method body at this point in compilation (it might not even get called). If typeof was changed to do less work, perhaps `is(typeof(...))` could do what you want.
Sep 16 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/16/20 12:59 PM, Nick Treleaven wrote:
 On Tuesday, 8 September 2020 at 18:59:00 UTC, Steven Schveighoffer wrote:
    void toString(Output)(Output output) if (isOutputRange!(Output, 
 dchar))
    {
       put(output, "hello, this is an S!"); // no import of std.range.put
    }
....
 if writeln checked using the proposed __traits(instantiates) instead 
 of __traits(compiles) on the call to S.toString(lockedTextOutput) (or 
 whatever it does), then instead of the wrong path, I get a compilation 
 error, telling me that my toString doesn't compile.
In theory, `is(typeof(S.init.toString(suitableOutputRange)))` could be true even if there is a semantic error in the body of your S.toString method. The typeof expression only needs to resolve the method then produce the type of that method. You specified the return type as `void` (not `auto`), so it is overkill to check the semantics of the method body at this point in compilation (it might not even get called). If typeof was changed to do less work, perhaps `is(typeof(...))` could do what you want.
Ehh... I don't care what it returns. I care that it can be called. The fact that I wrote void in my toy example is not important. But you bring up a good point. Something like "see if this lambda compiles" might included figuring out the return type of a function. Which in turn means to analyze the body (if it's an auto return). So the formulation of a constraint may have to avoid using "see if this lambda works" techniques to avoid having to compile everything. -Steve
Sep 16 2020
prev sibling next sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Sep 07, 2020 at 10:57:24AM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
[...]
 I was wondering, would it be a useful addition to have a way to say
 "can call" instead of "can compile"? In other words, this has a valid
 template IFTI match, regardless of whether it compiles or not. In
 which case, you can distinguish mismatch errors from implementation
 errors.
[...] Yes, yes, and yes! This could save hours of hair-tearing frustration when trying to debug a long UFCS chain where there's a typo in one of the lambdas. Having it fail the sig constraint because of a typo means you get an undecipherable error in the innards of Phobos, far away from the actual problem, rather than an error in the lambda's body in user code (which has been gagged because it just so happens to be inside __traits(compiles) or is(typeof(...))). T -- Always remember that you are unique. Just like everybody else. -- despair.com
Sep 07 2020
prev sibling next sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer 
wrote:
 I get a compilation error. But it's not in foo, it's in bar. It 
 says:

 Error: template instance onlineapp.bar!(foo) does not match 
 template declaration bar(alias x)()
   with x = foo(T)(T v)
   must satisfy the following constraint:
        __traits(compiles, x(1))

 [...]

 The problem is, if the intention is for it to pass, but the 
 implementation is bugged, the error message is useless. If the 
 thing I'm passing is a type with 200 LOC, and there's an error 
 buried in there somewhere, the compiler is telling me 
 "somewhere in this, it doesn't work". And sometimes those 
 function signatures and their constraints are hairy themselves.

 My usual steps taken at this point are to extract the 
 constraint template and actually try to call the functions in 
 the constraints with that type, and see what fails. This is 
 somewhat annoying, and sometimes difficult to do based on where 
 the code that implements the constraint actually is, and where 
 the code actually calling it is (I may have to duplicate a lot 
 of stuff).
The tedious-but-reliable way to handle this is to recompile with `-verrors=spec`, search the output for the original error message, and then scroll up until you find what you're looking for in the speculative errors. The only issue is that you have to wade through a lot of useless chaff to find the information you actually care about. IMO the best way to make this easier would be to have the compiler provide a "stack trace" of speculative errors for each failed constraint whenever a template fails to instantiate--maybe behind a switch (`-verrors=constraints`) to avoid clogging up the output in the common case. This has the advantage of working for *all* indirect errors like this, not just ones involving IFTI. To give a concrete example, `-verrors=constraints` would have helped me a lot with the problems I had to debug in SumType's copy constructors recently [1], whereas __traits(canCall) would have been completely useless. [1] https://github.com/pbackus/sumtype/issues/36
Sep 07 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/7/20 12:12 PM, Paul Backus wrote:
 On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
 I get a compilation error. But it's not in foo, it's in bar. It says:

 Error: template instance onlineapp.bar!(foo) does not match template 
 declaration bar(alias x)()
   with x = foo(T)(T v)
   must satisfy the following constraint:
        __traits(compiles, x(1))

 [...]

 The problem is, if the intention is for it to pass, but the 
 implementation is bugged, the error message is useless. If the thing 
 I'm passing is a type with 200 LOC, and there's an error buried in 
 there somewhere, the compiler is telling me "somewhere in this, it 
 doesn't work". And sometimes those function signatures and their 
 constraints are hairy themselves.

 My usual steps taken at this point are to extract the constraint 
 template and actually try to call the functions in the constraints 
 with that type, and see what fails. This is somewhat annoying, and 
 sometimes difficult to do based on where the code that implements the 
 constraint actually is, and where the code actually calling it is (I 
 may have to duplicate a lot of stuff).
The tedious-but-reliable way to handle this is to recompile with `-verrors=spec`, search the output for the original error message, and then scroll up until you find what you're looking for in the speculative errors. The only issue is that you have to wade through a lot of useless chaff to find the information you actually care about. IMO the best way to make this easier would be to have the compiler provide a "stack trace" of speculative errors for each failed constraint whenever a template fails to instantiate--maybe behind a switch (`-verrors=constraints`) to avoid clogging up the output in the common case. This has the advantage of working for *all* indirect errors like this, not just ones involving IFTI. To give a concrete example, `-verrors=constraints` would have helped me a lot with the problems I had to debug in SumType's copy constructors recently [1], whereas __traits(canCall) would have been completely useless. [1] https://github.com/pbackus/sumtype/issues/36
Thanks! This is the kind of response I'm looking for. I'm trying to understand what the issue and the solution was in that bug. I'm assuming once the compiler bugs were fixed, then you had to figure out why it wasn't compiling? I wasn't aware of -verrors=spec, but it sounds like it spitting out ALL speculative errors, not just when a specific call fails to build? While giving a searchable set of data, the situation is only slightly improved. It is true that if your constraint is the problem, then __traits(canCall) or whatever wouldn't help. But I don't see any constraint changes in that code. Perhaps you can elaborate on the specific issue and how you solved it? It's unclear to me from the diff and the bug discussion that the __traits(canCall) would not help. I would be OK with your proposal as well, and it actually could be done separately from the __traits(canCall) idea. Focusing the aim of the speculative errors might be useful in general -- like, give a specific instantiation line and parameters to debug, and only have it trace that one error, rather than having to search through pages of text. -Steve
Sep 07 2020
next sibling parent Paul Backus <snarwin gmail.com> writes:
On Monday, 7 September 2020 at 16:27:46 UTC, Steven Schveighoffer 
wrote:
 Thanks! This is the kind of response I'm looking for.

 I'm trying to understand what the issue and the solution was in 
 that bug. I'm assuming once the compiler bugs were fixed, then 
 you had to figure out why it wasn't compiling?
It was actually the reverse. SumType's copy constructors were failing due to a compiler bug, but the error I was getting was: src/sumtype.d(1412,4): Error: static assert: "`handlers[0]` of type `template` never matches" ...because the copy constructor uses `match` internally, and that's the error `match` gives you when your handler fails to speculatively compile. Turning on -verrors=spec allowed me to see the error *inside* the handler that was causing speculative compilation to fail: (spec:1) src/sumtype.d-mixin-407(407): Error: union Storage has constructors, cannot use { initializers }, use Storage( initializers ) instead ...which is what led me to diagnosing and, ultimately, fixing issue 20842 [1]. [1] https://issues.dlang.org/show_bug.cgi?id=20842
 I wasn't aware of -verrors=spec, but it sounds like it spitting 
 out ALL speculative errors, not just when a specific call fails 
 to build? While giving a searchable set of data, the situation 
 is only slightly improved.

 It is true that if your constraint is the problem, then 
 __traits(canCall) or whatever wouldn't help. But I don't see 
 any constraint changes in that code.
You're right; I was in a hurry when I wrote my post and didn't think it through enough. Really, what I want is to see speculative errors if and only if they occur during a failed attempt at non-speculative template instantiation. In other words, when a template fails to instantiate and causes compilation to fail, I would like to know why, but if compilation succeeds, I don't care about the details. So, `-verrors=spec-fatal`, maybe. It's possible you could even apply this recursively, and only show (spec:2) errors if they cause a failed template instantiation in (spec:1), etc. I haven't really thought it through, though; it's possible that would go too far and end up hiding information programmers actually want to see.
Sep 07 2020
prev sibling parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Sep 07, 2020 at 12:27:46PM -0400, Steven Schveighoffer via
Digitalmars-d wrote:
[...]
 I wasn't aware of -verrors=spec, but it sounds like it spitting out
 ALL speculative errors, not just when a specific call fails to build?
 While giving a searchable set of data, the situation is only slightly
 improved.
While it's great that we at least have -verrors=spec for those times when we're faced with a truly obtuse and impenetrable template error, IME it doesn't really improve the situation very much, because the real error is drowned in endless pages of other completely irrelevant errors before *and* after the relevant line(s). There have been times when I'd rather resort to other methods of narrowing down the problem (like copy-n-pasting and stepwise truncating a long UFCS chain until the problem is isolated, or just plain ole rewrite the darned code in a way that *doesn't* involve so many templates) than to engage in yet another search for the needle of an error in a veritable haystack of irrelevant speculative template error fluff. T -- Philosophy: how to make a career out of daydreaming.
Sep 08 2020
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer 
wrote:
 Thoughts?

 -Steve
I like your idea. It's interesting that this would actually be implementable in the library if we had an ability to retrieve a template's compile-time parameters. Then you could have something like: ----- void foo(T)(T v) { auto x = v.someProperty; } template isCallable (alias sym, Args...) { // create a syntactical "copy" of 'sym' void Proto (CompileTimeParams!sym)(RunTimeParams!sym) { } enum bool isCallable = is(typeof(Proto(Args))); } void bar(alias x)() if (isCallable!(x, 1)) { auto val = x(1); } void main () { bar!foo(); } ----- However: 1. We don't have a way to extract the compile-time parameters from a template / templated function 2. I'm not sure if there is an easy way to retrieve the run-time parameters either. There is `ParameterStorageClassTuple` but it's awkward to use. And regardless, a library solution will likely be very slow anyway. This belongs in the compiler.
Sep 08 2020
prev sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer 
wrote:
 Consider a template function like this:

 void foo(T)(T v)
 {
    auto x = v.someProperty;
 }

 [...]
This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg forum.dlang.org However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.
Sep 15 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/15/20 10:18 AM, WebFreak001 wrote:
 On Monday, 7 September 2020 at 14:57:24 UTC, Steven Schveighoffer wrote:
 Consider a template function like this:

 void foo(T)(T v)
 {
    auto x = v.someProperty;
 }

 [...]
This is already possible from a library solution, see https://forum.dlang.org/post/iribazqozceygujkvvjg forum.dlang.org However I imagine it is slow because of mixin operations + not well defined because it uses stringof, but if you just want it in some small project of yours you can just use that.
But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members. -Steve
Sep 15 2020
next sibling parent reply Jackel <jackel894_394 gmail.com> writes:
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven 
Schveighoffer wrote:
 I could probably switch to using "hasMember" in this use case, 
 which is likely to be correct for all purposes that I have. 
 It's just not applicable to a public library, which might use 
 things other than members.

 -Steve
You mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work. I do agree with what you are saying, in fact it almost seems like __traits(compiles) is a code smell. There's usually a better way to express it, and as is with the case you are talking about, a different feature would be more appropriate and provide better error messages.
Sep 15 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/15/20 8:20 PM, Jackel wrote:
 On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
 I could probably switch to using "hasMember" in this use case, which 
 is likely to be correct for all purposes that I have. It's just not 
 applicable to a public library, which might use things other than 
 members.
You mentioned UFCS, but if it is a template in a library then user defined UFCS shouldn't work. The only valid case I can think of would be for a built in array, using front() and friends. But the library would have to import those functions for it to work.
Yes, but input ranges and Phobos are not the only things that depend on UFCS. There's also a question of whether a free function can be called with a provided type, which has no analogue to __traits(hasMember).
 I do agree with what you are saying, in fact it almost seems like 
 __traits(compiles) is a code smell. There's usually a better way to 
 express it, and as is with the case you are talking about, a different 
 feature would be more appropriate and provide better error messages.
Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed. Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually). -Steve
Sep 15 2020
parent FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 16 September 2020 at 00:28:59 UTC, Steven 
Schveighoffer wrote:
 Yes, but input ranges and Phobos are not the only things that 
 depend on UFCS.

 There's also a question of whether a free function can be 
 called with a provided type, which has no analogue to 
 __traits(hasMember).

 I do agree with what you are saying, in fact it almost seems 
 like __traits(compiles) is a code smell. There's usually a 
 better way to express it, and as is with the case you are 
 talking about, a different feature would be more appropriate 
 and provide better error messages.
Yes, exactly. I've spent way more than my fair share unraveling via library surgery what the compiler should be able to do itself, as long as the proper tools existed. Don't get me wrong, I'd be fine with a solution that doesn't need a new feature (that would be ideal actually). -Steve
Hm! I may have something: ``` public template hasInstantiationFor(alias F, T) { import std.traits : ReturnType; struct NoMatch { } alias proxy = F; alias proxy(T) = () => NoMatch(); enum hasInstantiationFor = !is(ReturnType!(proxy!T) == NoMatch); } ``` I could swear this used to straight up not work, but it seems to work now, at least for small local tests. I can't put it into production because it runs into a flood of unrelated bugs if I try to use it in an actual project. I swear it never ends with this language... But I'll try to dustmite this and maybe in DMD 2.095 we'll have a solution that actually works. Though it only works for templates that are characterized by specialization, not template inconditions. As far as I can tell there's no way to see if a template matches inconditions, because there's no "fallback incondition" like there is with specializations. So canInstantiate is still needed.
Sep 15 2020
prev sibling parent reply WebFreak001 <d.forum webfreak.org> writes:
On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven 
Schveighoffer wrote:
 On 9/15/20 10:18 AM, WebFreak001 wrote:
 [...]
But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members. -Steve
huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.
Sep 15 2020
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/16/20 1:58 AM, WebFreak001 wrote:
 On Tuesday, 15 September 2020 at 19:12:04 UTC, Steven Schveighoffer wrote:
 On 9/15/20 10:18 AM, WebFreak001 wrote:
 [...]
But that's the issue. If I know what small set of types should work, then I can just write overloads for those types. It's when I don't know what type is going to be passed in. I could probably switch to using "hasMember" in this use case, which is likely to be correct for all purposes that I have. It's just not applicable to a public library, which might use things other than members.
huh where did you get that from? My proposed solution there just checks if you can call a function with the given arguments only if it's passing all the template constraints.
Oh, I misread what you were doing. I assumed you were using traits to get parameter information, and I didn't bother reading very well. Sorry! Indeed, this is a close solution. You would need to be worried about possible import issues (or overloads). Plus you might have unused functions that are not called. I'd also want to directly call the function instead. I may want to actually verify the linkage is correct. Something like: template doppleganger(alias fun) { mixin("private void doppleganger" ~ def[def.indexOf('(' .. $] ~ " {}"); } static assert(__traits(compiles, fun!foo(args))); Need a way to pass templates. But it does seem doable. As you point out, the stringof thing is pretty sketchy. But yes, this is pretty much what I would want to have. Just more of a solid feature than a hack ;) -Steve
Sep 16 2020
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/16/20 9:04 AM, Steven Schveighoffer wrote:
 static assert(__traits(compiles, fun!foo(args)));
ugh. static assert(__traits(compiles, doppleganger!foo(args)));
Sep 16 2020