Feature request: __traits(canInstantiate), like __traits(compiles) but without suppressing syntax errors

H. S. Teoh hsteoh at quickfur.ath.cx
Fri Jan 17 18:44:37 UTC 2020


On Fri, Jan 17, 2020 at 11:01:29AM -0500, Steven Schveighoffer via Digitalmars-d wrote:
[...]
> Yes, concepts would be useful, but also would be limited for how D
> does duck typing. D's constraints are very much like concepts, but
> without having to declare everything up front for use in the function.

IMO, not declaring everything up front for use is an anti-pattern.
Basically, by accepting a template parameter T and performing operations
on it, you're assuming the T supports said operations. If T doesn't
support such operations, you shouldn't be receiving it in the first
place. By not specifying what operations you expect T to support in your
sig constraints, you're basically declaring that you accept *any* T,
including one that supports no operations, yet you proceed to operate on
it, which ought to be an error (not just a silent failure to
instantiate).

IOW, you're actually making assumptions on what T supports, yet you
never declared such assumptions.  Conversely, if you declare no
assumptions on T, then neither should you be allowed to operate on it. A
parameter T received without any assumptions should be treated as an
opaque object that allows *no* operations.


[...]
> However, the real problem is things like typos that cause the function
> not to compile for reasons other than the intended failures.
> 
> For example:
> 
> T foo(T)(T x)
> {
>    return X + 5; // typo on parameter name
> }
> 
> This will NEVER compile, because X will never exist. The intention was
> for it to compile with types that support addition to integers (i.e.
> it's EXPECTED to fail for strings), but instead it fails for all
> types.

See, this is why this kind of code is bad.  You basically wish to accept
all T that support +, but by not declaring it as such, the compiler has
no way to know what you intended. So it assumes that you somehow expect
X to spring into existence given some magic value of T, and when that
never happens, the compiler always skips over foo.  Had you declared
that you expect T to support +, then the compiler would know to
instantiate foo when T==int, then it would have caught the typo as a
compile error.  As a bonus, your code would even accept custom types
that support +, without any further effort on your part.

Another side effect of not declaring assumptions up-front is that the
compiler cannot produce better error messages. You're stuck with error
gagging that hides typos, because the compiler simply doesn't have
enough information to know what was intended. What if you intended for
foo never to compile?  Without declaring any assumptions the compiler
couldn't know better.


> But it's hard for the compiler to deduce intention from such typos. It
> would be great if the compiler could figure out this type of error,
> but I don't think it can.

It can if there was a requirement that template arguments are not
allowed to be operated on unless their ability to support the operation
is either declared in a sig constraint, or else tested with an
appropriate static if condition.


> The only "fix" really is to judiciously unittest with the calls you
> expect to work. And that generally doesn't happen (we are a lazy
> bunch!).

Exactly, that's why you have to force users to declare what assumptions
they're making on the template parameters. It's more "convenient" to
just take the easy way and allow everything without checking, but it
only leads to pain and more pain down the road.


> Perhaps, an "expected" clause or something to ensure it compiles for
> the expected types like: expected(T == int), which wouldn't unittest
> anything, just make sure it can compile for something that is
> expected.

Just do this instead:

	unittest {
		// will generate compile error if instantiation fails:
		alias A = myTemplate!int;
	}


T

-- 
Кто везде - тот нигде.


More information about the Digitalmars-d mailing list