Thread overview
"Concepts" should not be part of function headers
2 days ago
monkyyy
1 day ago
H. S. Teoh
1 day ago
jmh530
1 day ago
monkyyy
1 day ago
monkyyy
1 day ago
Paul Backus
1 day ago
H. S. Teoh
1 day ago
jmh530
23 hours ago
An Pham
21 hours ago
jmh530
2 days ago

https://youtu.be/1p6So6vE5WM?si=tEbeBOQ-xXjJawMY

"if its a subset it automagically picks the right one because of our clever overload rules"

No, No, just No; every overload set with >5 memebers is a big ball of bugs such as to

These are type assertions; isComaparable!(T,U)=>bool could be made with a reusable error code by as template assertComaparable(T,U,string func=__FUNCTION__) =>
"max of int and string requires opCmp, consider:

  1. .to!string of the first argument
  2. .to!int of th 2nd argument
  3. implementing int.opCmp(string)
  4. implementing string.opCmp(int)
    "
1 day ago
On Wed, Oct 22, 2025 at 09:10:03AM +0000, monkyyy via Digitalmars-d-learn wrote:
> https://youtu.be/1p6So6vE5WM?si=tEbeBOQ-xXjJawMY
> 
> "if its a subset it automagically picks the right one because of our clever overload rules"
> 
> No, No, just No; every overload set with >5 memebers is a big ball of bugs such as `to`
> 
> These are *type assertions*; `isComaparable!(T,U)`=>bool could be made
> with a reusable error code by as `template
> assertComaparable(T,U,string func=__FUNCTION__)` =>
> "max of int and string requires opCmp, consider:
>   1) .to!string of the first argument
>   2) .to!int of th 2nd argument
>   3) implementing int.opCmp(string)
>   4) implementing string.opCmp(int)
> "

I've argued this before: many instances of overload sets in Phobos should not be overload sets at all, but should be single functions (or perhaps a *much* smaller overload set) with static if's inside the function body to dispatch to the various implementations, along with static assert's that provide helpful error messages for when the arguments don't match any of the static if conditions.

The user-facing function signature should describe the *logical* API, rather than implementation details such as "if input is a float, if input is an enum, ..." ad nauseaum.  Logically, std.conv.to is supposed to accept *all* types (even if the current implementation may or may not accept *everything*), so the function signature should be:

	T to(T,U)(U data) { ... }

*without* any signature constraints.  All the stuff like "if U is floating-point, if U is enum, if U is struct, ...", etc., are implementation details, that should be left to static if's inside the function body rather than clutter the API with an incomprehensibly large overload set.


T

-- 
Why is it that all of the instruments seeking intelligent life in the universe are pointed away from Earth? -- Michael Beibl
1 day ago

On Wednesday, 22 October 2025 at 14:40:58 UTC, H. S. Teoh wrote:

>

On Wed, Oct 22, 2025 at 09:10:03AM +0000, monkyyy via Digitalmars-d-learn wrote:

>

https://youtu.be/1p6So6vE5WM?si=tEbeBOQ-xXjJawMY

"if its a subset it automagically picks the right one because of our clever overload rules"

No, No, just No; every overload set with >5 memebers is a big ball of bugs such as to

These are type assertions; isComaparable!(T,U)=>bool could be made
with a reusable error code by as template assertComaparable(T,U,string func=__FUNCTION__) =>
"max of int and string requires opCmp, consider:

  1. .to!string of the first argument
  2. .to!int of th 2nd argument
  3. implementing int.opCmp(string)
  4. implementing string.opCmp(int)
    "

I've argued this before: many instances of overload sets in Phobos should not be overload sets at all, but should be single functions (or perhaps a much smaller overload set) with static if's inside the function body to dispatch to the various implementations, along with static assert's that provide helpful error messages for when the arguments don't match any of the static if conditions.

The user-facing function signature should describe the logical API, rather than implementation details such as "if input is a float, if input is an enum, ..." ad nauseaum. Logically, std.conv.to is supposed to accept all types (even if the current implementation may or may not accept everything), so the function signature should be:

T to(T,U)(U data) { ... }

without any signature constraints. All the stuff like "if U is floating-point, if U is enum, if U is struct, ...", etc., are implementation details, that should be left to static if's inside the function body rather than clutter the API with an incomprehensibly large overload set.

T

This thread is probably more appropriate for the General forum than this one.

Anyway, I haven't watched the Youtube video, but I'm not quite as negative on C++ concepts as others.

I think you put this well, but I'm not so sure it is necessarily a negative with respect to concepts, or otherwise putting template constraints in the signature, provided there is a single function. For instance, it makes sense for to to work with any type, but maybe it makes sense to constrain a function that should only work on floating point types to say that in the signature (and then if integer types are added later, then it can put a numeric type in the signature and have the interger/floating point handling happen inside the function as an implementation detail).

I think it's a question of best practices and making sure they are widely understood.

The lingering issue is how to manage the organization of the code and corresponding template bloat. If people follow your advice and then have the toImpl helper functions within static if blocks to improve organization, then that might increase template bloat compared to having separate functions.

In one thing I was working on (that got really annoying to finish off and my time got limited so I haven't had a chance to finish it off), I was dealing with types that (basically) could be any kind of floating point type, with different allocation strategies, and (more-or-less) different ways to iterate through them. And then passing this off to functions that only vary based on floating point type. So you had to take the original types, convert them slightly, and then pass them off to the downstream function. But then you throw const/immutable into dealing with it, which added another set of complexity. I think what I ended up doing was a separate function signature depending on the allocation type. But then since each function could take two inputs, it ended up with like 6. And then those all ended up calling down to one function.

Long story short, it was a real headache.

If it were possible to just have a one line function signature with all the implementation complexity in the static if's, while being well-organized and not too much template bloat, then that would have been lovely.

1 day ago

On Wednesday, 22 October 2025 at 16:43:31 UTC, jmh530 wrote:

>

This thread is probably more appropriate for the General forum than this one.

it was 3 am, someone copy and paste

1 day ago
On Wednesday, 22 October 2025 at 14:40:58 UTC, H. S. Teoh wrote:
> 
> *logical* API,

What it?
1 day ago

On Wednesday, 22 October 2025 at 14:40:58 UTC, H. S. Teoh wrote:

>

The user-facing function signature should describe the logical API, rather than implementation details such as "if input is a float, if input is an enum, ..." ad nauseaum. Logically, std.conv.to is supposed to accept all types (even if the current implementation may or may not accept everything), so the function signature should be:

T to(T,U)(U data) { ... }

without any signature constraints. All the stuff like "if U is floating-point, if U is enum, if U is struct, ...", etc., are implementation details, that should be left to static if's inside the function body rather than clutter the API with an incomprehensibly large overload set.

In the specific case of std.conv.to, the user-facing template is unconstrained. Its signature is:

template to(T)

However, the internal function that to forwards to, toImpl, does use template constraints to create an incomprehensible overload set.

1 day ago
On Thu, Oct 23, 2025 at 12:07:51AM +0000, Paul Backus via Digitalmars-d-learn wrote:
> On Wednesday, 22 October 2025 at 14:40:58 UTC, H. S. Teoh wrote:
> > The user-facing function signature should describe the *logical* API, rather than implementation details such as "if input is a float, if input is an enum, ..." ad nauseaum.  Logically, std.conv.to is supposed to accept *all* types (even if the current implementation may or may not accept *everything*), so the function signature should be:
> > 
> >     T to(T,U)(U data) { ... }
> > 
> > *without* any signature constraints.  All the stuff like "if U is floating-point, if U is enum, if U is struct, ...", etc., are implementation details, that should be left to static if's inside the function body rather than clutter the API with an incomprehensibly large overload set.
> 
> In the specific case of `std.conv.to`, the user-facing template *is* unconstrained. Its signature is:
> 
>     template to(T)
> 
> However, the internal function that `to` forwards to, `toImpl`, does use template constraints to create an incomprehensible overload set.

So the problem wasn't solved, just moved from the public API to the internal API.  Since it's no longer user-facing, those overloads really should be separately-named functions instead IMNSHO. Not 50 overloads on `toImpl`.


T

-- 
If blunt statements had a point, they wouldn't be blunt...
1 day ago

On Thursday, 23 October 2025 at 02:06:44 UTC, H. S. Teoh wrote:

>

[snip]

So the problem wasn't solved, just moved from the public API to the internal API. Since it's no longer user-facing, those overloads really should be separately-named functions instead IMNSHO. Not 50 overloads on toImpl.

T

What's the advantage of making them separately-named functions?

23 hours ago

On Thursday, 23 October 2025 at 12:38:08 UTC, jmh530 wrote:

>

On Thursday, 23 October 2025 at 02:06:44 UTC, H. S. Teoh wrote:

>

[snip]

So the problem wasn't solved, just moved from the public API to the internal API. Since it's no longer user-facing, those overloads really should be separately-named functions instead IMNSHO. Not 50 overloads on toImpl.

T

What's the advantage of making them separately-named functions?

  1. Easy to search for/jump to implementation
  2. No need template constraint(s) -> faster compile

Happy coding

21 hours ago

On Thursday, 23 October 2025 at 13:51:40 UTC, An Pham wrote:

>

On Thursday, 23 October 2025 at 12:38:08 UTC, jmh530 wrote:

>

On Thursday, 23 October 2025 at 02:06:44 UTC, H. S. Teoh wrote:

>

[snip]

So the problem wasn't solved, just moved from the public API to the internal API. Since it's no longer user-facing, those overloads really should be separately-named functions instead IMNSHO. Not 50 overloads on toImpl.

T

What's the advantage of making them separately-named functions?

  1. Easy to search for/jump to implementation
  2. No need template constraint(s) -> faster compile

Happy coding

I get 1, that's a good point, but not sure on 2.

Just looking at toImpl functions in std.conv, they all have template constraints. Let's suppose you convert them all to functions with separate names but no template constraints. But they're still all templated functions, so anyone working on the code base needs to make sure not to use those functions improperly (working in the module that is, they're all private functions). So I'd wonder, why get rid of the template constraints even if you change the name? Just sets yourself for potential future bugs. The speed up for compilation is probably more related to a smaller set for overload resolution. Might be a bit better for error messages, but not sure offhand.

Speaking of error messages, I think that's really the one place where concepts really are supposed to shine.