Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Finest C++

71 views
Skip to first unread message

Bonita Montero

unread,
Apr 17, 2023, 12:14:04 AM4/17/23
to
template<typename T>
concept scalar = is_scalar_v<T>;

template<scalar ... TS>
common_type_t<TS ...> min( TS ... values )
{
common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
((min = values < min ? values : min), ...);
return min;
}

Alf P. Steinbach

unread,
Apr 17, 2023, 4:52:46 AM4/17/23
to
I guess you're posting this because of a perceived elegance in the code.

What is the point of the initialization of `min` when the first thing
that happens after is that `min` is assigned to?

Was perhaps the intent to reduce the number of assignments via e.g.

((values < min ? min = values : 0), ...);

And why do you name a local variable the same as the function?

One reason to write your own `min` rather than just using `std::min`
could be to support a mix of signed and unsigned. But the presented code
doesn't do that, so (because it supports a mix of arbitrary scalar
argument types) it introduces a bug vector compared to `std::min`. Why?


- Alf (somewhat perplexed)

Frederick Virchanza Gotham

unread,
Apr 17, 2023, 5:38:12 AM4/17/23
to
On Monday, April 17, 2023 at 9:52:46 AM UTC+1, Alf P. Steinbach wrote:
>
> And why do you name a local variable the same as the function?

In my dayjob I find myself having to do the following a lot:

#include <.....loads of header files......>
#undef min
#undef max
#include <algorithm>

Just making the point that 'min' gets abused a lot.

Bonita Montero

unread,
Apr 17, 2023, 7:06:17 AM4/17/23
to
Am 17.04.2023 um 10:52 schrieb Alf P. Steinbach:

> What is the point of the initialization of `min` when the first thing
> that happens after is that `min` is assigned to?

This is optimized away.

> Was perhaps the intent to reduce the number of assignments via e.g.
>     ((values < min ? min = values : 0), ...);

You need a fold-expression or a generic recursive function for that.
Fold expressions are easier to handle.

> And why do you name a local variable the same as the function?

Because there's no problem with that since the function name isn't
referred from inside.

> One reason to write your own `min` rather than just using
> `std::min` could be to support a mix of signed and unsigned.

std::min accepts only two values or an initializer-list which
has more syntax.

Bonita Montero

unread,
Apr 17, 2023, 7:09:50 AM4/17/23
to
This is a corrected version:

template<typename ... TS>
common_type_t<TS ...> min( TS const &... values )
{
using ct_t = common_type_t<TS ...>;
common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
((min = (ct_t)values < min ? (ct_t)values : min), ...);
return min;
}

I forgot to cast values to ct_t so that the common value is determined
pair-wise and not for the whole statement.

Alf P. Steinbach

unread,
Apr 17, 2023, 7:10:13 AM4/17/23
to
`-D NOMINMAX`

But better make that part of a wrapper for `<windows.h>`.


- Alf

Bonita Montero

unread,
Apr 17, 2023, 7:18:16 AM4/17/23
to
And the min/max-macros aren't usable for more than two values.
I prefer to define NOMINMAX inside the source file before the
include since there's not something to forget for the build
-process. Usually when I use numeric_limit<X>::min()/max().
I think that's really awkward for someone who doesn't know
that Windows.h defines this macros by default.

Frederick Virchanza Gotham

unread,
Apr 17, 2023, 7:27:25 AM4/17/23
to
On Monday, April 17, 2023 at 12:10:13 PM UTC+1, Alf P. Steinbach wrote:
>
> But better make that part of a wrapper for `<windows.h>`.


When I need to access the Win32 API directly, I make a separate translation called "windows_bomb_chamber.c", and that's the only source file that includes <windows.h>. Then I put my own function declarations inside "windows_bomb_chamber.h".

Alf P. Steinbach

unread,
Apr 17, 2023, 7:34:24 AM4/17/23
to
On 2023-04-17 1:08 PM, Bonita Montero wrote:
> Am 17.04.2023 um 10:52 schrieb Alf P. Steinbach:
>
>> What is the point of the initialization of `min` when the first thing
>> that happens after is that `min` is assigned to?
>
> This is optimized away.
>
>> Was perhaps the intent to reduce the number of assignments via e.g.
>>      ((values < min ? min = values : 0), ...);
>
> You need a fold-expression or a generic recursive function for that.
> Fold expressions are easier to handle.

How come fold expressions are easier to handle than a fold expression?


>> And why do you name a local variable the same as the function?
>
> Because there's no problem with that since the function name isn't
> referred from inside.

It's a waste of time + annoyance for a human.

Same as the lack of parenthesis around the choice expression.

The effective precedence of the ternary operator in various usages is a
subtlety of the language that few programmers remember.

So when I choose to take you seriously, such as this time, I have to
engage in detective work to figure out if you have a bug or just are
training for the yearly obfuscated code contest.

It's easy to write clear code for this: add parentheses.


>> One reason to write your own `min` rather than just using
>> `std::min`  could be to support a mix of signed and unsigned.
>
> std::min accepts only two values or an initializer-list which
> has more syntax.

Writing and using obscure code with a likely-to-manifest standard
library name conflict in order to avoid a pair of braces, doesn't make
sense to me, but as I noted at the beginning, perhaps you did this for
the perceived elegance or beauty, like artwork.


- Alf

David Brown

unread,
Apr 17, 2023, 9:02:40 AM4/17/23
to
On 17/04/2023 10:52, Alf P. Steinbach wrote:
> On 2023-04-17 6:15 AM, Bonita Montero wrote:
>> template<typename T>
>> concept scalar = is_scalar_v<T>;
>>
>> template<scalar ... TS>
>> common_type_t<TS ...> min( TS ... values )
>> {
>>      common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
>>      ((min = values < min ? values : min), ...);
>>      return min;
>> }
>
> I guess you're posting this because of a perceived elegance in the code.
>
> What is the point of the initialization of `min` when the first thing
> that happens after is that `min` is assigned to?

It is needed because, given arbitrary types, there is no general choice
of the initial value for the fold. (It is not a "C++ fold" because C++
only supports folds on infix binary operators, but it is a "fold" in the
more general programming sense.)

But the code then re-uses the first value, which is unnecessary - if
comparisons are expensive, it is a waste. It is better to use a
head+tail recursion pattern here:

template<scalar T, scalar ... TS>
auto xmin(T t, TS ... values )
{
auto x = xmin(values ...);
return t < x ? t : x;
}


A t3(A a, B b, C c) {
return xmin(a, b, c);
}


>
> Was perhaps the intent to reduce the number of assignments via e.g.
>
>     ((values < min ? min = values : 0), ...);
>

No - see above.

> And why do you name a local variable the same as the function?

To confuse readers, I expect. I assume it is the same reason there
appears to be a "using std;" in effect, and then a function whose name
matches one in the "std" namespace (i.e., "std::min").

>
> One reason to write your own `min` rather than just using `std::min`
> could be to support a mix of signed and unsigned. But the presented code
> doesn't do that, so (because it supports a mix of arbitrary scalar
> argument types) it introduces a bug vector compared to `std::min`. Why?
>

Mixing signed and unsigned types is always a risk in C++. In a reusable
function like this, it could make sense to protect against it.

Otherwise, gcc's "-Wconversion -Wsign-conversion" are useful tools.


Alf P. Steinbach

unread,
Apr 17, 2023, 9:49:40 AM4/17/23
to
On 2023-04-17 3:02 PM, David Brown wrote:
> On 17/04/2023 10:52, Alf P. Steinbach wrote:
>> On 2023-04-17 6:15 AM, Bonita Montero wrote:
>>> template<typename T>
>>> concept scalar = is_scalar_v<T>;
>>>
>>> template<scalar ... TS>
>>> common_type_t<TS ...> min( TS ... values )
>>> {
>>>      common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
>>>      ((min = values < min ? values : min), ...);
>>>      return min;
>>> }
>>
>> I guess you're posting this because of a perceived elegance in the code.
>>
>> What is the point of the initialization of `min` when the first thing
>> that happens after is that `min` is assigned to?
>
> It is needed because, given arbitrary types, there is no general choice
> of the initial value for the fold.  (It is not a "C++ fold" because C++
> only supports folds on infix binary operators, but it is a "fold" in the
> more general programming sense.)

My comment was not the smartest one I've ever produced.

I think I was still confused by the parsing of the ?:.


> But the code then re-uses the first value, which is unnecessary - if
> comparisons are expensive, it is a waste.  It is better to use a
> head+tail recursion pattern here:
>
> template<scalar T, scalar ... TS>
> auto xmin(T t, TS ... values )
> {
>     auto x = xmin(values ...);
>     return t < x ? t : x;
> }
>
>
> A t3(A a, B b, C c) {
>     return xmin(a, b, c);
> }

That looks better, yes. But relies on tail recursion optimization that
is not guaranteed. So even better just e.g.

template< signed_scalar... Args >
constexpr auto xmin( Args... args )
-> auto
{ return min({ static_cast<<common_type_t<First, Rest...>>( args
)... }); }

Disclaimer: did not let compiler check that.


>> Was perhaps the intent to reduce the number of assignments via e.g.
>>
>>      ((values < min ? min = values : 0), ...);
>>
>
> No - see above.
>
>> And why do you name a local variable the same as the function?
>
> To confuse readers, I expect.  I assume it is the same reason there
> appears to be a "using std;" in effect, and then a function whose name
> matches one in the "std" namespace (i.e., "std::min").
>
>>
>> One reason to write your own `min` rather than just using `std::min`
>> could be to support a mix of signed and unsigned. But the presented
>> code doesn't do that, so (because it supports a mix of arbitrary
>> scalar argument types) it introduces a bug vector compared to
>> `std::min`. Why?
>>
>
> Mixing signed and unsigned types is always a risk in C++.  In a reusable
> function like this, it could make sense to protect against it.
>
> Otherwise, gcc's "-Wconversion -Wsign-conversion" are useful tools.

I'd just limit the function to signed type arguments, as shown above.


Cheers,

- Alf

Alf P. Steinbach

unread,
Apr 17, 2023, 9:52:32 AM4/17/23
to
On 2023-04-17 3:49 PM, Alf P. Steinbach wrote:
>
>     template< signed_scalar... Args >
>     constexpr auto xmin( Args... args )
>         -> auto
>     { return min({ static_cast<<common_type_t<First, Rest...>>( args
> )... }); }
>
> Disclaimer: did not let compiler check that.

template< signed_scalar... Args >
constexpr auto xmin( Args... args )
-> auto
{ return min({ static_cast<<common_type_t<Args...>>( args )... }); }


That editing error was of course Donald Trump's work.

Same disclaimer.


- Alf

Bonita Montero

unread,
Apr 17, 2023, 11:31:52 AM4/17/23
to
The following code avoids superfluous temporary-copies.

template<typename ... TS>
common_type_t<TS ...> xmin( TS const &... values )
{
using ct_t = common_type_t<TS ...>;
ct_t min = get<0>( tuple<TS ...>( values ... ) );
if constexpr( (same_as<TS, common_type_t<TS ...>> && ...) )
((min = values < min ? values : min), ...);
else
{
auto nextMin = [&]( TS const &value )
{
ct_t cvtValue( value );
if( cvtValue < min )
min = cvtValue;
};
(nextMin( values ), ...);
}
return min;
}

Mut...@dastardlyhq.com

unread,
Apr 17, 2023, 11:42:03 AM4/17/23
to
On Mon, 17 Apr 2023 13:11:36 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 17.04.2023 um 06:15 schrieb Bonita Montero:
>> template<typename T>
>> concept scalar = is_scalar_v<T>;
>>
>> template<scalar ... TS>
>> common_type_t<TS ...> min( TS ... values )
>> {
>>     common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
>>     ((min = values < min ? values : min), ...);
>>     return min;
>> }
>
>This is a corrected version:
>
>template<typename ... TS>
>common_type_t<TS ...> min( TS const &... values )
>{
> using ct_t = common_type_t<TS ...>;
> common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
> ((min = (ct_t)values < min ? (ct_t)values : min), ...);
> return min;
>}

One of the reasons Perl has been comprehensively killed by Python except in a
few niche and legacy applications is because people got sick of trying to
decipher the barely comprehensible line noise that "gurus" had written in order
to show off. Unfortunately C++ seems to be heading in that direction.

Sometimes more can be less when it comes to code.

Bonita Montero

unread,
Apr 17, 2023, 11:48:25 AM4/17/23
to
Am 17.04.2023 um 17:41 schrieb Mut...@dastardlyhq.com:

>> template<typename ... TS>
>> common_type_t<TS ...> min( TS const &... values )
>> {
>> using ct_t = common_type_t<TS ...>;
>> common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
>> ((min = (ct_t)values < min ? (ct_t)values : min), ...);
>> return min;
>> }

> One of the reasons Perl has been comprehensively killed by Python except in a
> few niche and legacy applications is because people got sick of trying to
> decipher the barely comprehensible line noise that "gurus" had written in order
> to show off. Unfortunately C++ seems to be heading in that direction.

If you halfweay know C++17 this doesn't look like Swahili.


Mut...@dastardlyhq.com

unread,
Apr 17, 2023, 11:58:27 AM4/17/23
to
You're missing the point. There's enough work in trying to figure out the
logic of code itself when a problem arises. Thats only made worse by someone
doing mental masturbation with meta programming facilities. C++ is a huge
language now and just getting bigger and no one person can learn all of its
dusty corners so using fold expressions when in 99% of cases a standard loop
iterating an array/container would work just as well is assinine.

Bonita Montero

unread,
Apr 17, 2023, 12:10:08 PM4/17/23
to
Am 17.04.2023 um 17:58 schrieb Mut...@dastardlyhq.com:

> You're missing the point. There's enough work in trying to figure out the
> logic of code itself when a problem arises. Thats only made worse by someone
> doing mental masturbation with meta programming facilities. ...

This "mental masturbation" leads to much less code on the side of the
caller. The developer using such a function doesn't need to know whats
going on with the code but just how to use it, i.e. being able to read
the signature. Large parts of the standar library also look like that.


Mut...@dastardlyhq.com

unread,
Apr 17, 2023, 12:25:07 PM4/17/23
to
On Mon, 17 Apr 2023 18:11:56 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 17.04.2023 um 17:58 schrieb Mut...@dastardlyhq.com:
>
>> You're missing the point. There's enough work in trying to figure out the
>> logic of code itself when a problem arises. Thats only made worse by someone
>> doing mental masturbation with meta programming facilities. ...
>
>This "mental masturbation" leads to much less code on the side of the

Whooooosh...

A regex can save a lot of code but is often unintelligable to anyone except the
person who wrote it.


Bonita Montero

unread,
Apr 17, 2023, 1:52:20 PM4/17/23
to
Regex-Matching is straightforward and doesn't need
much template aerobatics inside the function.


Chris M. Thomasson

unread,
Apr 17, 2023, 7:22:45 PM4/17/23
to
LOL! ;^)

Malcolm McLean

unread,
Apr 18, 2023, 3:10:23 AM4/18/23
to
On Monday, 17 April 2023 at 14:02:40 UTC+1, David Brown wrote:
> On 17/04/2023 10:52, Alf P. Steinbach wrote:
> > On 2023-04-17 6:15 AM, Bonita Montero wrote:
> >> template<typename T>
> >> concept scalar = is_scalar_v<T>;
> >>
> >> template<scalar ... TS>
> >> common_type_t<TS ...> min( TS ... values )
> >> {
> >> common_type_t<TS ...> min = get<0>( tuple<TS ...>( values ... ) );
> >> ((min = values < min ? values : min), ...);
> >> return min;
> >> }
> >
> > I guess you're posting this because of a perceived elegance in the code.
> >
> > What is the point of the initialization of `min` when the first thing
> > that happens after is that `min` is assigned to?
> It is needed because, given arbitrary types, there is no general choice
> of the initial value for the fold. (It is not a "C++ fold" because C++
> only supports folds on infix binary operators, but it is a "fold" in the
> more general programming sense.)
>
> But the code then re-uses the first value, which is unnecessary - if
> comparisons are expensive, it is a waste. It is better to use a
> head+tail recursion pattern here:
>
> template<scalar T, scalar ... TS>
> auto xmin(T t, TS ... values )
> {
> auto x = xmin(values ...);
> return t < x ? t : x;
> }
>
Don't you need a termination condition here?
Real question, I'd be surprised if you made such a simple error.
But the code doesn't make any sense to someone not familiar
with the intricacies?

Mut...@dastardlyhq.com

unread,
Apr 18, 2023, 4:16:35 AM4/18/23
to
On Mon, 17 Apr 2023 19:54:06 +0200
Bonita Montero <Bonita....@gmail.com> wrote:
>Am 17.04.2023 um 18:24 schrieb Mut...@dastardlyhq.com:
>> On Mon, 17 Apr 2023 18:11:56 +0200
>> Bonita Montero <Bonita....@gmail.com> wrote:
>>> Am 17.04.2023 um 17:58 schrieb Mut...@dastardlyhq.com:
>>>
>>>> You're missing the point. There's enough work in trying to figure out the
>>>> logic of code itself when a problem arises. Thats only made worse by
>someone
>>>> doing mental masturbation with meta programming facilities. ...
>>>
>>> This "mental masturbation" leads to much less code on the side of the
>>
>> Whooooosh...
>>
>> A regex can save a lot of code but is often unintelligable to anyone except
>the
>> person who wrote it.
>
>Regex-Matching is straightforward and doesn't need
>much template aerobatics inside the function.

Face <- palm.

Never mind, you Just Don't Get It.

Bonita Montero

unread,
Apr 18, 2023, 7:30:10 AM4/18/23
to
You don't need to facepalm.
The complexity of regex-matching is different than what I've shown.

Bonita Montero

unread,
Apr 18, 2023, 8:18:19 AM4/18/23
to
Comparing min against the next value may make a object conversion
necessary and sometimes the type of the next compared valie is the
common type. So I decided to check this for each "iteration":

template<typename ... TS>
common_type_t<TS ...> xmin( TS const &... values )
{
using ct_t = common_type_t<TS ...>;
ct_t min = get<0>( tuple<TS ...>( values ... ) );
auto nextMin = [&]<typename T>( T const &value )
{
if constexpr( same_as<T, common_type_t<TS ...>> )
if( value < min )
min = value;
else;
else
{
ct_t cvtValue( value );
if( cvtValue < min )
min = cvtValue;
}
};
(nextMin( values ), ...);
return min;
}

I guess there's nothing more to improve.

0 new messages