implicit conversion

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Tue Aug 12 14:31:01 PDT 2014


On Tue, Aug 12, 2014 at 08:23:30PM +0000, Jonathan M Davis via Digitalmars-d-learn wrote:
> On Tuesday, 12 August 2014 at 19:03:58 UTC, H. S. Teoh via
> Digitalmars-d-learn wrote:
> >tl;dr: there are so many ways template code can go wrong, that I
> >don't it justifies blaming alias this for problems.
> 
> Allowing implicit conversions makes the problem much worse IMHO. It
> makes it far too easy to write a template constraint which passes due
> to the implicit conversion (even if an implicit conversion wasn't
> explicitly checked for) but then have the function fail to work
> properly because the implicit conversion never actually takes place
> within the function (and if the template constraint doesn't explicitly
> test for an implicit conversion, then the argument that the value
> should have been explicitly converted doesn't hold). Fortunately, in
> many cases, the result is a compilation error rather than weird
> behavior, but in some cases, it will be weird behavior - especially
> when the code involved is highly templatized and generic.
[...]

Y'know, after seeing the recent problem with deprecated functions in
template code, and now this, I'm starting to reconsider whether Concepts
might have been a better way to go. The good thing about concepts is
that the template body won't even compile if it makes unfounded
assumptions on the type that aren't given by what the concept defines.

In the current template system, code like this would easily compile:

	auto badCode(T)(T t)
	{
		// Does T even support such an operation? Who knows. The
		// compiler will happily accept this code, and if you
		// only unittest this function with numeric types,
		// you'll never know there was a problem.
		return t+1;
	}

In a concepts-based system, however, this code wouldn't compile, even
before you instantiate any templates. You'd have to define a concept
that supports the opBinary!"+"(int) operation before the compiler would
accept the code. And doing so enforces all incoming types to conform to
that concept or be instantly rejected. This causes the templated code to
be unable to do anything with the incoming type that isn't explicitly
specified in the concept, thus preventing unfounded assumptions. It
furthermore solves the alias this problem, because it would be clear to
the compiler which concept the function is supposed to be operating on,
and if the incoming type implicitly converts to something that
implements that concept, then the compiler knows to perform the
conversion first before perform an operation on it.

Now, it's true that concepts-based systems have their limitations.  But
I wonder if it's possible to extend a concepts-based system to be more
powerful, in the same way D has enhanced and developed C++ templates in
novel ways.

One way I can think of, that kinda sits between the current duck-typing
template system and a full-fledged concepts system, is one where
incoming types do not have to declare what concept they implement (thus,
a kind of duck-typing), but template functions are not allowed to
operate on an incoming type unless they declare a concept it must
conform to. Hypothetical syntax:

	// Concept definition
	concept InputRange(E) {
		alias ElementType = E;
		bool empty;
		E front;
		void popFront();
	}

	// Concrete type that implements InputRange(E)
	// N.B.: don't need to declare what concept it implements: this
	// makes it compatible with current code.
	struct MyRange {
		@property bool empty() { ... }
		@property int front() { ... }
		void popFront() { ... }

		// N.B.: this is *not* part of the concept definition
		@property MyRange save() { ... }
	}

	auto r = rangeFunc(MyRange.init);

	auto rangeFunc(R : InputRange!E, E)(R range)
	{
		while (!range.empty) // OK, .empty defined in InputRange!E
		{
			auto e = range.front; // OK, .front defined in InputRange!E

			if (someCond) {
				// ERROR: even though MyRange does
				// define .save, InputRange doesn't, and
				// we have only declared R to be an
				// InputRange, so .save is an undefined
				// operation on R.
				auto tmp = range.save;
			}

			// OK: .popFront is defined in InputRange!E
			range.popFront();
		}
		return ...;
	}


T

-- 
There are two ways to write error-free programs; only the third one works.


More information about the Digitalmars-d-learn mailing list