30 comments

[ 3.0 ms ] story [ 82.0 ms ] thread
I guess the answer is “it depends”. Operators are great when done in good taste and can be a complete nightmare if used too much or in bad taste.
I admit I only skimmed the article, but it looks like the only implicit operators discussed are implicit conversions.

There are plenty of implicit conversions in most languages. The C# feature being discussed is the ability to define your own implicit conversions.

I was expecting something like the "x y" or "xy" mathematical notation that means "x*y" (or x×y if you prefer).

When things are cheap, I have some hope that they are actually healthy.

When they are free, I know that there's a huge pile of externalities I can't even begin to imagine yet.

Javascript gets itself (and us) into trouble all the time with implicit conversions.

C# uses the syntax `operator bool` to define conversion so sometimes they get called operator accidentally.
You mean it uses `operator bool` where `bool` is the target type of the conversion, yes?

I have no problem with the idea that conversions are operators. My point is that the title suggests something far more general that what the body of the article actually discusses. If the title had said "Implicit Conversions" rather than "Implicit Operators", it would have been much clearer and I wouldn't have commented.

I don't like anything implicit in programming languages. Being explicit does not make code unreadable. Being implicit does make code hard to truly understand and requires being a language expert. What's worse, implicit code often pretends to be simple, while in reality it's not.
The general review of anything, "It would have been twice as good if it was half as long" applies.
Not in the general case, no. Not in programming. I would much rather review (and maintain) 20 lines of clear, readable code than 3 lines of obscure clever code. More lines isn't better, but in the limit fewer lines is very often worse.
Three lines of clever, readable code is also better than 20 lines of obscure boilerplate code.
In particular if that boilerplate code spans multiple projs and type inheritances for something that really could be done in about three lines. Boilerplate has its own obscurity problems.
destructuring is helpful

[1, 2] = somefun();

Decimal amount = m where this means Decimal amount = m.amount, seems less so.

How do I know that's implicit without tracing the type back?

Implicit can make things hard to understand meaning it should be used with caution.

But allowing CustomInt i = 0; instead of CustomInt i = CustomInt::Zero; or worse CustomInt i = static_cast<CustomInt>(i); isn't a bad thing.

For me this looks fine:

var i = CustomInt.valueOf(0)

Can’t you do `auto i = CustomInt(0)`? Then all of the construction is explicit (and in a single place in the statement) without repeating.
At the risk of being a pedant, I think your argument is too absolutist for it to really mean anything.

The exact same argument can be used by a sufficiently stubborn greybeard to bemoan the fact that C implicitly decides what registers to use for variables, as well as by a more "modern" developer to complain about == vs === in JS.

Surely you agree these levels of implicitness are not the same?

Can't wait to need code lawyers because we decided to turn perfectly fine programming languages into common language where its necessary an entire profession to decide if that was what was actually intended when being written or not
If you have ever worked in C++, you will find there are numerous Code Lawyers whose main business is to review all C++ for minor C++2030 infractions, missing optimisations and lack of use of the most obtuse language feature when you do things like:

  int ii = 0;
I think you meant

    int ii{};
I think you meant

    auto ii{};
Not at all. I meant

    std::common_type_t<bool, char> ii{};
Not sure this one is actually valid. You can't infer a type. Maybe in C++40 where types are so 30s?
C++ addresses this with the `explicit` specifier: all single-argument constructors are inherently conversion operators so don't need to be labeled "implicit" as they are in C#.

This makes sense: people are already unsurprised when they initialize a float with an integer. `float foo = 1;` doesn't put a 1 bit in the lsb, but initializes the float to 1.0.

But sometimes (often!) you don't want that, so specify your constructor as `explicit` and then it won't convert something else to satisfy your constructor's argument specification.

(comment deleted)
Having a vector with a sized based constructor transform an integer into a vector is surprising.
Yeah, I find that weird too — everybody does. It’s a legacy of the early 90s.

But the case under discussion is single-argument constructors, which can also be considered conversion operators.

No, the topic was "implicit isn't the right default"

It was a mistake that can't be fixed saying "C++ doesn't need implicit as a keyword so it is better" is wrong when explicit is the better default if you need one.

Scala had a lot of implicit stuff before Scala 3, but while very powerful it was also very tricky and hard to understand at times. In Scala 3 there is still a lot of implicit features, but a bit less powerful and with a bit more explicit syntax.

When I see how some younger developers in our company struggle with some magic of the Spring Framework, I doubt that implicit code pays such a large dividend.

The spring framework is by far the ugliest code ive ever had the displeasure of trying to understand.

Just an absolute horrific mess of nonesense from top to bottom.

I write C++ including the conventions around the strong units library we use. What this article promote as “better” seems pretty bad. They dont like `Money m = new Money(10.0m); decimal amount = m.Amount;` and images prefer `Money m = 10.0m; decimal amount = m;`. While it’s nice to not have to say `new`, the implicit conversion in and out of strong units is terrifying and defeats the purpose. For example, while I generally like C++’s `std::chrono` library, it s `duration` types have a `.count()` member function to extract the number. But if you change the type from `std::chrono::seconds` to `std::chrono::milliseconds`, then `.count()` silently changes from “number of seconds” to “number of milliseconds”. For full safety, you need to name the units to “unpack” them. E.g., `Speed_mmps speed = 10.0_mm / 2.0_s; float speed_mm_per_s = getCount<Speed_mmps>(speed);` where if the type of `speed` changed to furlongs per fortnight, the `getCount<Speed_mmps>` call would either not compile or be correct up to floating-point precision.