Concepts: Investigating implicit type-name introduction

348 views
Skip to first unread message

mihailn...@gmail.com

unread,
Jun 25, 2018, 3:20:29 PM6/25/18
to ISO C++ Standard - Future Proposals
Concept type-name introducer is a proposed new mechanism to introduce multiple template arguments with a single expression

Mergeable{In,In2,Out}
Out merge(In, In2, Out);

or eventually 

template<Mergeable{In,In2,Out}>
Out merge(In, In2, Out);

In any case the result will be the same as

template<class In, class In2, class Out>
  requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

The main motivation for an introducer is to "reduce the verbosity of requires clause".
Second motivation is that "this concept type name introducer is necessary to avoid cut&paste and macros."

However, the main motivation is served not by the introducer itself, but by introduction of tailored concepts.
The second motivation is understandable - if we have such a repeated pattern some people might use a macro and others will cut and paste. 

That said, the name introducers come with problems on their own.
  • Completely new syntax that needs learning. A bit better with template<> as it is already the place for name introduction. 
  • New syntax not for a new feature!
  • Completely "flat" and inflexible - no composition or decomposition. As a result all type information about the names is actually always hidden. A bit better in the template<> variant, because one can add other names/constrains.
  • Extremely confusing "dual use" of concepts - sometimes they look like a template instantiation, sometimes they look like something b/w initializer list and structured bindings.
  • The syntax visually overloads already quite common syntax - Something{}, class Something{};
Given all this, the difference b/w standard notation with tailored concept VS name introducer does not seem big and significant enough.

Still, undeniably there is repetition that might lead to some errors.
Can we have minimum solution to that alone? Can we make template<> optional if requires is used?

In other words

requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

Will introduce In, In2 and Out

It seems plausible.
The main problem are name collisions - if some of the names are already defined in the enclosing scope.

using Out = …;

requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

Let's investigate.

Can we use the previous name instead of introducing our own. Of course not.
Can we ignore the previous name. It depends.

Consider

using Num = …;

requires Number<Num>
Num max(Num, Num);

Here we can! We introduce a new Num and that is always correct.

However

using MyClass = …;

requires Assignable<Class, MyClass>
auto func(Class);

How about here?
Can we still blindly override? No, obviously.
Can we be "smart" and use only the type that is not already defined? No, because at any point someone might define Class and change the meaning, hell the definition, of our function. 

We can only fail to compile. 
What can we further do, what are the resolutions.

A. Do nothing. This variant is not that unacceptable. First, name collision is highly unlikely IF we only intend to define local names (as introducers do).
Consider, Mergeable<In,In2,Out>, these are perfectly acceptable local names, yet terrible names to put in global/namespace scope!
In other words, if we stop just here, we only lose the ability to use already defined types (introducers can't have that either), but this can easily be worked around with a (sub-)tailored concept!
 
requires AssignableToMyClass<Class>
auto func(Class);

Done.
At this point however we are still waaay more powerful then simple introducer, as we have most of the requires clause powers!

requires Assignable<Value_type<It>> //< note we use concepts inline, 
    || Something<It> //< we can OR them and so on.
auto func(It);

We have almost full control over the concept(s) we want to use as "terse". This is a massive win.

B. If we find the do-nothing not good enough, we have to introduce some syntactical hints.

Two options 

Var 1. Mark the names we introduce. 

requires Assignable<typename Class, MyClass>
auto func(Class);

or, to be terse

requires Assignable<{Class}, MyClass>
auto func(Class);

Var 2. Mark the types we use. 

requires Assignable<Class, using MyClass>
auto func(Class);

Note that this is needed IFF, MyClass is user-defined type (not std type) AND the concept has multiple arguments AND the type is in the same scope.

By example

using MyClass = …;

requires Concept<MyClass> //< OK, one argument, introduces template<typename MyClass>
MyClass max(MyClass, MyClass); 

MyClass max(MyClass, MyClass) 
    requires Concept<MyClass>; //< OK, NOT a template, just constrained function 

requires Assignable<Class, int> //< OK, std type
auto func(Class);

requires Assignable<Class, size_t> //< OK, std type
auto func(Class);

requires Assignable<Class, std::string> //< OK, different scope
auto func(Class);

requires Concept<Class> && Assignable<Class, MyClass> //< OK with Var 1 only
auto func(Class);

requires Assignable<Class, MyClass> //< error, need to pin either the name (Var 1) or the type (Var 2)
auto func(Class);


What the advantages of is B. ? 

Well, it let us have the full power of requires.

On a practical level, assuming tailored concepts are used to eliminate verbosity, we can have tailored concepts that accept type arguments.
This is not a small win, as we eliminate a bunch of sub-tailored concepts like AssignableToMyClass.

Needless to say nether composition nor type arguments are possible with current name introducers. 

Between Var 1 and Var 2, which one is better.
Var 1 has the advantage that one can write a clause, immune to outside name introductions as it can pin names. It can also take advantage of names, already introduced. 
Main disadvantage is that, if terse syntax is not introduced, it can become verbose.

Var 2 has the advantage that statistically, use of outside types in introducing concepts is rare. Even if types are used, the number of introduced names will probably be more then types.
In that sense Var 2 is completely minimalistic - you rarely have to write it and you write less.
Another advantage is that there is no optional syntax - name is either implicitly introduced or explicitly used as type. There are no unpinned types (fails to compile), where in Var 1 one can have both optionally and implicitly pinned names.
The main disadvantage is that you have to use standard notation instead in order to really pin names.

That is pretty much it. 

I believe it is a viable take, as we introduce less and gain more and the disadvantages (some collision) seem to be not a big problem in practice (local names are not good namespace-scope names) 

Feedback is welcome 

P.S Only concepts introduce names, not arbitrary expression like sizeof(T) or constexpr functions.

Nicol Bolas

unread,
Jun 25, 2018, 4:22:43 PM6/25/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Monday, June 25, 2018 at 3:20:29 PM UTC-4, mihailn...@gmail.com wrote:
Concept type-name introducer is a proposed new mechanism to introduce multiple template arguments with a single expression

Mergeable{In,In2,Out}
Out merge(In, In2, Out);

or eventually 

template<Mergeable{In,In2,Out}>
Out merge(In, In2, Out);

In any case the result will be the same as

template<class In, class In2, class Out>
  requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

The main motivation for an introducer is to "reduce the verbosity of requires clause".
Second motivation is that "this concept type name introducer is necessary to avoid cut&paste and macros."

However, the main motivation is served not by the introducer itself, but by introduction of tailored concepts.
The second motivation is understandable - if we have such a repeated pattern some people might use a macro and others will cut and paste. 

That said, the name introducers come with problems on their own.
  • Completely new syntax that needs learning. A bit better with template<> as it is already the place for name introduction. 
  • New syntax not for a new feature!
I don't think repeating the same criticism is helpful.
  • Completely "flat" and inflexible - no composition or decomposition. As a result all type information about the names is actually always hidden. A bit better in the template<> variant, because one can add other names/constrains.
  • Extremely confusing "dual use" of concepts - sometimes they look like a template instantiation, sometimes they look like something b/w initializer list and structured bindings.
  • The syntax visually overloads already quite common syntax - Something{}, class Something{};
Given all this, the difference b/w standard notation with tailored concept VS name introducer does not seem big and significant enough.

Still, undeniably there is repetition that might lead to some errors.
Can we have minimum solution to that alone? Can we make template<> optional if requires is used?

In other words

requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

Will introduce In, In2 and Out

How is this not new syntax? Because it doesn't use a new symbol? It's using existing symbols in new ways, just like {} does. For example:

1. `Name<...>` always means "specialize this template". But that's not happening here. Or rather it is, but that's not all that's happening here.
2. `<>` doesn't declare identifiers without explaining what those identifiers actually are. That is, without `typename`, `template`, or an actual type's name.
3. This new form of `<>` only works in a `requires` clause.
4. `requires` now has a third meaning. It's similar to the original meaning, but because it's applied as a prefix, it now performs double-duty as a template header. At least `template<Mergable>` looks like a template header.

mihailn...@gmail.com

unread,
Jun 26, 2018, 3:34:21 AM6/26/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com


On Monday, June 25, 2018 at 11:22:43 PM UTC+3, Nicol Bolas wrote:


On Monday, June 25, 2018 at 3:20:29 PM UTC-4, mihailn...@gmail.com wrote:
Concept type-name introducer is a proposed new mechanism to introduce multiple template arguments with a single expression

Mergeable{In,In2,Out}
Out merge(In, In2, Out);

or eventually 

template<Mergeable{In,In2,Out}>
Out merge(In, In2, Out);

In any case the result will be the same as

template<class In, class In2, class Out>
  requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

The main motivation for an introducer is to "reduce the verbosity of requires clause".
Second motivation is that "this concept type name introducer is necessary to avoid cut&paste and macros."

However, the main motivation is served not by the introducer itself, but by introduction of tailored concepts.
The second motivation is understandable - if we have such a repeated pattern some people might use a macro and others will cut and paste. 

 
That said, the name introducers come with problems on their own.
  • Completely new syntax that needs learning. A bit better with template<> as it is already the place for name introduction. 
  • New syntax not for a new feature!
I don't think repeating the same criticism is helpful.

It is not the same. A syntax might be new, but not a radically new construct, effectively unparsable without explanations and examples.
Event if for a new feature radically new syntax is to be questioned. But it is not a new feature, that's the second problem.  
  • Completely "flat" and inflexible - no composition or decomposition. As a result all type information about the names is actually always hidden. A bit better in the template<> variant, because one can add other names/constrains.
  • Extremely confusing "dual use" of concepts - sometimes they look like a template instantiation, sometimes they look like something b/w initializer list and structured bindings.
  • The syntax visually overloads already quite common syntax - Something{}, class Something{};
Given all this, the difference b/w standard notation with tailored concept VS name introducer does not seem big and significant enough.

Still, undeniably there is repetition that might lead to some errors.
Can we have minimum solution to that alone? Can we make template<> optional if requires is used?

In other words

requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

Will introduce In, In2 and Out

How is this not new syntax? Because it doesn't use a new symbol? It's using existing symbols in new ways, just like {} does. For example:

No, {} is used in a new context, creating new construct. No new constructs are created here, simply using-is-a-declaration. 
 

1. `Name<...>` always means "specialize this template". But that's not happening here. Or rather it is, but that's not all that's happening here.
2. `<>` doesn't declare identifiers without explaining what those identifiers actually are. That is, without `typename`, `template`, or an actual type's name. 
 
The same argument can be made about both name introducers and even shorthand notation.

template<template<typename> class X> concept C2 = true;

template<C2 X> void f2(); //< WTF is X, must look the concept

I am willing to argue, this is actually more clear in that particular case:

requires C2<X> void f2(); //< X is what is written b/w the <> of the concept! 

The fact that a concept, which is a template, is used as such is a huge plus for requires in general.
 
3. This new form of `<>` only works in a `requires` clause.

It is not about <>, it is exclusively about 'requires'. 
Alternatively,  if we would feel better, might be template Mergeable<In,In2,Out>  Out merge(In, In2, Out);.
 
4. `requires` now has a third meaning. It's similar to the original meaning, but because it's applied as a prefix, it now performs double-duty as a template header. At least `template<Mergable>` looks like a template header.
 
But requires is already part of the template header!
 

I don't see any of these particularly troubling.  Yes, there is some new context-sensitive interpretation, which is still better then new syntax for a bonus feature. 

But lets equate the new syntax (introducers) to these new rules in terms of how troubling they are. These new rules give us more power, full power
They also eliminate the question "what is a name introducer". This topic no longer exist. Yes, new question arises - where are these template arguments coming from, but that is a trivial question to answer.

Zhihao Yuan

unread,
Jun 27, 2018, 5:44:13 AM6/27/18
to std-pr...@isocpp.org, mihailn...@gmail.com
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);

constrained-parameter:
  qualified-concept-name ... identifieropt
  qualified-concept-name identifieropt default-template-argumentopt
  <ins>qualified-concept-name consecutive-identifier-list</ins>

consecutive-identifier-list:
  identifier
  consecutive-identifier-list identifier

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________


‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

mihailn...@gmail.com

unread,
Jun 27, 2018, 6:40:40 AM6/27/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com, z...@miator.net


On Wednesday, June 27, 2018 at 12:44:13 PM UTC+3, Zhihao Yuan wrote:
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);

I actually like that idea. 
  • We can pretend "name introducers" do not exist as a sperate thing, a construct. You name the concept, after that you have to name all the types that are in the <> of that concept. That's it, nothing new to learn!
  • We go away from that "its an action", "it does something" feel of the introducers and the slight unease "is it Mergeable actually instantiated in some way here?" Is Mergeable "called" in some way? Is this related to struct. bindings? Can I use <> in place of {} and vice versa? 
  • We go away (for now) from all-powerful-alternative-syntax-for-templates (the in-place)
I believe this is the most natural evolution of the shorthand syntax. Of course some will argue listing things without commas is weird, but the alternatives are not better. 

hubert.rein...@gmail.com

unread,
Jun 27, 2018, 5:07:39 PM6/27/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com, z...@miator.net
On Wednesday, June 27, 2018 at 5:44:13 AM UTC-4, Zhihao Yuan wrote:
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);
"Functions" whose arity are non-obvious are (in my opinion) an unfortunate aspect of certain languages.

K&R C gives us the perfectly usable idea of a list of identifiers that introduce parameters; it even allows further attributes of the parameters to be declared afterwards:
int f(a, b, c)
  int a, b, c;
{ return a + b + c; }


Lines (statements ending in semicolons) could be used to apply constraints.

template <In, In2, Out, Other>
{ Mergeable <In, In2, Out>; typename Other; }
Out merge(In, In2, Out);


That is perhaps not as short, but still shorter than:
template <typename In, typename In2, typename Out, typename Other>
requires Mergeable<In, In2, Out>
Out merge(In, In2, Out);


Zhihao Yuan

unread,
Jun 27, 2018, 7:09:50 PM6/27/18
to hubert.rein...@gmail.com, ISO C++ Standard - Future Proposals, mihailn...@gmail.com
I found that view making sense, but I would argue that the
arity of the template parameters in a function template is
the least useful arity to the readers... If I remember correctly
the standard library only includes a few interfaces which
require you to supply the first template argument (like T in
get<T>(x)), all the other template parameters are deduced.

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________


‐‐‐‐‐‐‐ Original Message ‐‐‐‐‐‐‐

mihailn...@gmail.com

unread,
Jun 28, 2018, 4:54:33 AM6/28/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com, z...@miator.net


On Thursday, June 28, 2018 at 12:07:39 AM UTC+3, hubert.rein...@gmail.com wrote:
On Wednesday, June 27, 2018 at 5:44:13 AM UTC-4, Zhihao Yuan wrote:
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);
"Functions" whose arity are non-obvious are (in my opinion) an unfortunate aspect of certain languages.

K&R C gives us the perfectly usable idea of a list of identifiers that introduce parameters; it even allows further attributes of the parameters to be declared afterwards:
int f(a, b, c)
  int a, b, c;
{ return a + b + c; }


Lines (statements ending in semicolons) could be used to apply constraints.

template <In, In2, Out, Other>
{ Mergeable <In, In2, Out>; typename Other; }
Out merge(In, In2, Out);

The idea is to be able to introduce the types entirely in terms of the concepts they represent so there would be less chance of errors. 
For instance someone might pass Other in the place of Out or swap In and In2 (this might or might not be a problem) and so on.

In any case, if we don't care of the repetition

template <typename In, In2, Out, Other>

  requires Mergeable<In, In2, Out>
Out merge(In, In2, Out);

is the best option

Nicol Bolas

unread,
Jun 28, 2018, 10:44:20 PM6/28/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
On Tuesday, June 26, 2018 at 3:34:21 AM UTC-4, mihailn...@gmail.com wrote:
On Monday, June 25, 2018 at 11:22:43 PM UTC+3, Nicol Bolas wrote:
On Monday, June 25, 2018 at 3:20:29 PM UTC-4, mihailn...@gmail.com wrote:
  • Completely "flat" and inflexible - no composition or decomposition. As a result all type information about the names is actually always hidden. A bit better in the template<> variant, because one can add other names/constrains.
  • Extremely confusing "dual use" of concepts - sometimes they look like a template instantiation, sometimes they look like something b/w initializer list and structured bindings.
  • The syntax visually overloads already quite common syntax - Something{}, class Something{};
Given all this, the difference b/w standard notation with tailored concept VS name introducer does not seem big and significant enough.

Still, undeniably there is repetition that might lead to some errors.
Can we have minimum solution to that alone? Can we make template<> optional if requires is used?

In other words

requires Mergeable<In,In2,Out>
Out merge(In, In2, Out);

Will introduce In, In2 and Out

How is this not new syntax? Because it doesn't use a new symbol? It's using existing symbols in new ways, just like {} does. For example:

No, {} is used in a new context, creating new construct. No new constructs are created here, simply using-is-a-declaration.

"New syntax", "new feature", "new context", "new construct", these phrases don't mean anything in an absolute sense of the language.

You want to cause a template instantiation (ie: what `ConceptName<...>` is) to create template parameters. That is a new thing; it is nothing like C++ has ever done before with template instantiation. But you don't want to make this a general feature of template instantiation. No, it only works on concept variable templates, and only when used in a `requires` clause. Or maybe only from within certain `requires` clauses; those replacing template headers.

It doesn't matter if you can argue that this is or isn't a "new context" or "new construct" or whatever by some arbitrary definition. What matters is behavior and the ability of people to understand what's going on. It is undeniably novel behavior for a template instantiation to introduce identifiers using <> syntax. As such, I see no reason why this is any easier to understand than using a different token to introduce such identifiers.

Users will have to learn it either way. Your hypothetical time traveler will be equally baffled. Though at least with `{}`, they'll immediately know something special is going on and thus be able to track down what it is.

At least with a different token, concepts can consistently introduce names anywhere. And it will use a syntax that always means "introduce names". If you see {}, you're getting names introduced. If you see <>, a template is being instantiated. This is a simple and easily digestible rule.

With yours, you're not sure if <> means "introduce names" in addition to "template arguments" without looking at the context.

1. `Name<...>` always means "specialize this template". But that's not happening here. Or rather it is, but that's not all that's happening here.
2. `<>` doesn't declare identifiers without explaining what those identifiers actually are. That is, without `typename`, `template`, or an actual type's name. 
 
The same argument can be made about both name introducers and even shorthand notation.

My point was to show that your syntax fails at meeting your own standards. These are the reasons you yourself gave for why the other terse syntaxes are wrong, yet your own syntax falls afoul of them.

My issue with your syntax has nothing to do with it being "new" or whatever. The main reason why I don't like your syntax is because it overloads the meaning of <>, but only in a special place. Say what you will about Herb's syntax, but his {} can be used anywhere that concepts are employed. Consistency and uniformity are an aspect that your (and Bjarne's template introduction syntax of any form) syntax lacks.

template<template<typename> class X> concept C2 = true;

template<C2 X> void f2(); //< WTF is X, must look the concept

I am willing to argue, this is actually more clear in that particular case:

requires C2<X> void f2(); //< X is what is written b/w the <> of the concept! 

The fact that a concept, which is a template, is used as such is a huge plus for requires in general.

I fail to see how this is a benefit. Your new syntax isn't shorter than the current syntax. And neither syntax makes it clear what `X` is. If the user is unfamiliar with `C2`, they have to ask "WTF is X", regardless of which syntax is used.

3. This new form of `<>` only works in a `requires` clause.

It is not about <>, it is exclusively about 'requires'.

And yet, it uses <>, which has a well-established meaning. And that well-established meaning is being changed by `requires`.

Alternatively,  if we would feel better, might be template Mergeable<In,In2,Out>  Out merge(In, In2, Out);.

It's still using <> to do something that <> doesn't normally do.

4. `requires` now has a third meaning. It's similar to the original meaning, but because it's applied as a prefix, it now performs double-duty as a template header. At least `template<Mergable>` looks like a template header.
 
But requires is already part of the template header!

There's a difference between "part of" something and "is something". Your `requires` is not merely "part of" the template header; it is a template header. Just like Bjarne's original template introduction syntax made `Mergable{...}` be the actual template header.

I don't see any of these particularly troubling.  Yes, there is some new context-sensitive interpretation, which is still better then new syntax for a bonus feature. 

But lets equate the new syntax (introducers) to these new rules in terms of how troubling they are. These new rules give us more power, full power.

How? What new "power" do they give us that Herb's syntax couldn't? Yes, P0745 specifically says that they're type names, but that doesn't have to be that way. There's no reason it couldn't be extended to arbitrary template parameters of a concept.

They also eliminate the question "what is a name introducer". This topic no longer exist. Yes, new question arises - where are these template arguments coming from, but that is a trivial question to answer.

You skipped a question: how is a template instantiation legal if some of the arguments are names that don't exist? That's a question that your syntax creates, because it overloads existing syntax that already has a well-established meaning.

There is a difference between taking a syntax that couldn't be used at all and giving it meaning, and taking a syntax that already exists with some meaning and changing what that means. Personally, I prefer the former to the latter.

You also skipped "when can we use that ability of template instantiation to create template parameters?" Or more to the point, "why can I only use it in a prefixed `requires` clause?"

This is reason #1 why I dislike template introduction syntax: it creates new syntax that can only be used in a narrow way. Herb's syntax at least has the benefit of being consistent, rather than this giant wart you can sometime use.

On Wednesday, June 27, 2018 at 6:40:40 AM UTC-4, mihailn...@gmail.com wrote:
On Wednesday, June 27, 2018 at 12:44:13 PM UTC+3, Zhihao Yuan wrote:
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);

I actually like that idea.

So, your definition of "new syntax" does not include "list of identifiers without separators in places where that's not normally allowed".

I'm starting to get the impression that your position is not based on problems with "new syntax" in general or even terse templates as an idea. It seems more like "not curly braces!" That you'll accept any solution as long as it doesn't involve curly braces (and probably parentheses or square brackets).

I'm not exactly enamored with curly braces myself. But you have to admit that it's a lot less novel of syntax compared to "list of identifiers with no separators that somehow introduce template parameter names based on a concept". At least curly-braced-bound lists are a thing in C++ that people are used to. They may have different meaning, but it is a normal feature of the language to see `{identifier, identifier, identifier}` in code.

By contrast, `identifier identifier identifier` doesn't happen in C++. Not as a list of things, at least.

Zhihao Yuan

unread,
Jun 29, 2018, 3:22:07 AM6/29/18
to std-pr...@isocpp.org, mihailn...@gmail.com

I merely used some CS knowledge here.  Any finite
automaton which can accept

    Sortable A B

is able to consume

    Sortable A

as a prefix, but not all finite automatons which accept

    Sortable{A, B}

also consume

    Sortable A

So I guess it’s not too unsound to treat “Sortable A B” as,
grammatically, a generalization to “Sortable A”?

Yes, I know, 1-tuple doesn’t need braces.  I will be
convinced when I can write (int 1).

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________

 

From: Nicol Bolas <jmck...@gmail.com>
Sent: Thursday, June 28, 2018 9:44 PM

    template <Mergeable In In2 Out, class Other>

    Out merge(In, In2, Out);

 

I actually like that idea.

 

So, your definition of "new syntax" does not include "list of identifiers without separators in places where that's not normally allowed".

 

[…]

 

I'm not exactly enamored with curly braces myself. But you have to admit that it's a lot less novel of syntax compared to "list of identifiers with no separators that somehow introduce template parameter names based on a concept". At least curly-braced-bound lists are a thing in C++ that people are used to. They may have different meaning, but it is a normal feature of the language to see `{identifier, identifier, identifier}` in code.

mihailn...@gmail.com

unread,
Jun 29, 2018, 4:18:15 AM6/29/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
And we have to leave it there. We agree to disagree. For me (a bit weird/awkward) new rules and full power is better, for you clear new syntax is better even if it risks creating new dialect for template creation. 
I can't see how anyone can convince the other of the opposite and that's OK.
 

You also skipped "when can we use that ability of template instantiation to create template parameters?" Or more to the point, "why can I only use it in a prefixed `requires` clause?"

This is reason #1 why I dislike template introduction syntax: it creates new syntax that can only be used in a narrow way. Herb's syntax at least has the benefit of being consistent, rather than this giant wart you can sometime use.

On Wednesday, June 27, 2018 at 6:40:40 AM UTC-4, mihailn...@gmail.com wrote:
On Wednesday, June 27, 2018 at 12:44:13 PM UTC+3, Zhihao Yuan wrote:
Outside the debate of the you-know-that syntax, I think this
demand is easy to fulfill.  I think we can allow multi-argument
constrained parameters like this:

    template <Mergeable In In2 Out, class Other>
    Out merge(In, In2, Out);

I actually like that idea.

So, your definition of "new syntax" does not include "list of identifiers without separators in places where that's not normally allowed".

I'm starting to get the impression that your position is not based on problems with "new syntax" in general or even terse templates as an idea. It seems more like "not curly braces!" That you'll accept any solution as long as it doesn't involve curly braces (and probably parentheses or square brackets).

I was never ageist terse/natural as I said in the other topic. But, yes am against it if it introduces new syntax for humans only and/or new constructs.
Introducers are a new construct, and I believe we can talk about that in absolute sense.  
The reason you like in-place is the fact that it makes this new construct usable across the rest of the templates language, creating consistency and giving power.
Here we agree to disagree, and there is no point repeating my counter arguments. 


I'm not exactly enamored with curly braces myself. But you have to admit that it's a lot less novel of syntax compared to "list of identifiers with no separators that somehow introduce template parameter names based on a concept". At least curly-braced-bound lists are a thing in C++ that people are used to. They may have different meaning, but it is a normal feature of the language to see `{identifier, identifier, identifier}` in code.

I am hard press to remember a single place where identifiers are introduced with a similar syntax.
Sure we have seen a lot of {something, something}, but that was, until now, an act of initialization (ever since C) or enum {something, something}; an act of definition (sure identifiers are introduced, but are in the scope, given by the braces. We reaffirmed that rule with enum class).
 
This inevitably creates mental confusion (to some extend, at some point, for some people, etc) as it literally uses the same syntax in a completely new context for completely new meaning.
 

By contrast, `identifier identifier identifier` doesn't happen in C++. Not as a list of things, at least.

True, but a list inside a list like that does not happen either. We are both on the same scope but inside another 

In any case you have to admit

template<Concept T, class A>

template<Concept T U, class A>

are pretty self explanatory and the questions that arise for a newcomer can be answered by deduction. 

Where braces do rise questions impossible to answer without learning. 
Instantiation? Inline Definition? Scope? Relation to <>? Alternative to <>? 

Tony V E

unread,
Jun 29, 2018, 8:53:32 AM6/29/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
template<Concept T U, class A>

where do I put const?

template<Concept T const U, class A>

Would the committee be forced to make a final decision on
 east const vs const west?

Sent from my BlackBerry portable Babbage Device
Sent: Friday, June 29, 2018 4:18 AM
To: ISO C++ Standard - Future Proposals
Subject: [std-proposals] Re: Concepts: Investigating implicit type-name introduction

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/e69010d8-c8b0-4a1e-999f-af5dff75a8f2%40isocpp.org.

Jake Arkinstall

unread,
Jun 29, 2018, 9:08:20 AM6/29/18
to std-pr...@isocpp.org, mihailn...@gmail.com

On Fri, 29 Jun 2018, 13:53 Tony V E, <tvan...@gmail.com> wrote:
template<Concept T U, class A>

where do I put const?

You don't... do you? Maybe I'm missing out on something fundamental, but const doesn't make sense to me in this context.

Tony V E

unread,
Jun 29, 2018, 12:17:17 PM6/29/18
to Standard Proposals
On Fri, Jun 29, 2018 at 9:08 AM, Jake Arkinstall <jake.ar...@gmail.com> wrote:

On Fri, 29 Jun 2018, 13:53 Tony V E, <tvan...@gmail.com> wrote:
template<Concept T U, class A>

where do I put const?

You don't... do you? Maybe I'm missing out on something fundamental, but const doesn't make sense to me in this context.

ah maybe not


template<Concept T const U, class A>

Would the committee be forced to make a final decision on
 east const vs const west?

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

unread,
Jul 3, 2018, 9:48:29 PM7/3/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
Well, the new mailing strongly suggests that we all lose. Well, for a certain definition of "lose", I suppose.

What Sutter, Stroustrup, and 10 others (seriously, is that the longest "authors" list in proposal history?) seem to have agreed on is basically verbose terse syntax.

The core syntax is that adjective/noun stuff that was thrown around, discarded, and then resurrected again. So instead of saying `ConceptName param`, or even `ConceptName{} param`, you have to say `ConceptName auto param`. I am very thankful it isn't `ConceptName typename param`.

Of course, this creates a bit of confusion around function parameters and return types, since they both use the same `auto` keyword that means different things in different contexts. Granted, that would have happened regardless of terse syntax. And this syntax does have one unique advantage in this respect: you get to use `decltype(auto)` return deduction alongside `ConceptName` constraints. So that's something.

Also, we get to use `ConceptName auto/typename/template` when constraining explicit template parameters. This unpleasantness is mitigated because `typename` is optional; the most common case of a type constraint doesn't need the extra verbosity. I dislike this, but I understand the importance of being able to tell the difference.

Oh, and we get to do `ConceptName auto variableName` too. Though like with return types, it does let us use `decltype(auto)` syntax, which is occasionally nice. And the `auto` is at least not required. And uniformly not required: in all of the places where `auto` does value deduction rather than "I'm a template parameter", the `auto` is optional.

OK yes, this is all more verbose. But hey, at least we didn't introduce any "new syntax", something that would be genuinely useful like a way to actually name these deduced types. That would have been terrible ;)

Yes, I would have preferred Herb's syntax, because it means I don't have to use `decltype`, but I can live with this.

mihailn...@gmail.com

unread,
Jul 5, 2018, 3:09:35 AM7/5/18
to ISO C++ Standard - Future Proposals, mihailn...@gmail.com
Oh, well it is not that bad, in any case I would take func(Concept auto) over template func(Concept), although it is a syntax I doubt would have be suggested if we worked on a clear slate. I also really hope, only one concept is allowed

Now if only we can get rid of introducers in favor Zhihao Yuan suggestion. 
 

Ville Voutilainen

unread,
Jul 5, 2018, 3:54:32 AM7/5/18
to ISO C++ Standard - Future Proposals
On 4 July 2018 at 04:48, Nicol Bolas <jmck...@gmail.com> wrote:
> Well, the new mailing strongly suggests that we all lose. Well, for a
> certain definition of "lose", I suppose.
>
> What Sutter, Stroustrup, and 10 others (seriously, is that the longest
> "authors" list in proposal history?) seem to have agreed on is basically
> verbose terse syntax.

Well, it's as long as I could make it in the time it took to discuss
that paper with all those authors and revise it before the
pre-meeting deadline, which took a little over a week.

> The core syntax is that adjective/noun stuff that was thrown around,
> discarded, and then resurrected again. So instead of saying `ConceptName
> param`, or even `ConceptName{} param`, you have to say `ConceptName auto
> param`. I am very thankful it isn't `ConceptName typename param`.

The 'typename' part was deliberately omitted; the adjective approach suggested
'ConceptName typename T param', but we already have a way to name a constrained
type with 'template <ConceptName T>'.

> Of course, this creates a bit of confusion around function parameters and
> return types, since they both use the same `auto` keyword that means
> different things in different contexts. Granted, that would have happened
> regardless of terse syntax. And this syntax does have one unique advantage

Correct.

> in this respect: you get to use `decltype(auto)` return deduction alongside
> `ConceptName` constraints. So that's something.

Sure, filling that gap was one of the goals; make auto work, and make
'constrained auto' work,
and by extension, make 'constrained decltype(auto)' work.

> Also, we get to use `ConceptName auto/typename/template` when constraining
> explicit template parameters. This unpleasantness is mitigated because

There is no 'ConceptName typename' nor 'ConceptName template' in that proposal.

> Oh, and we get to do `ConceptName auto variableName` too. Though like with
> return types, it does let us use `decltype(auto)` syntax, which is
> occasionally nice. And the `auto` is at least not required. And uniformly
> not required: in all of the places where `auto` does value deduction rather
> than "I'm a template parameter", the `auto` is optional.

This was high on the wishlist, at least for variable declarations. For
a constrained deduced
return type, it's not necessarily that fundamental, but nice-to-have.

> Yes, I would have preferred Herb's syntax, because it means I don't have to
> use `decltype`, but I can live with this.

The biggest reason for this paper was to try and find an approach that
more people can live with.

Corentin

unread,
Jul 5, 2018, 5:48:17 AM7/5/18
to std-pr...@isocpp.org
 I personally would have loved to have both 'Concept T' and 'Concept typename T'  such that 'Concept T' would be a shorthand notation for 'Concept typename T'  aka, you can omit the 'typename'.

This is purely for teaching, learning & consistency because we could just say 'stick a ConceptName to the left of any declaration to constrain it.

<auto x>; //value
<typename T>; // type
<Concept auto x; //constrained variable;
<Concept typename T>; // constrained type;
<Concept T> //  type, as a sane default short hand in template parameter lists
-> Concept  {}  // value, as a sane, unambiguous default for functions return type
Concept auto x = ...; //variable

I think it would make it easier to teach ( start with the explicit notation, then mention the shorthands), because then concepts really "constrain a declaration", wherever the declaration is,
And I think these kinds of simple, consistent rules make the language more accessible, even if we expect experienced developers to always reach for the shorthand syntax.


--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Joel Falcou

unread,
Jul 5, 2018, 5:55:49 AM7/5/18
to std-pr...@isocpp.org
I fully agree with this idea. Teachability should be facilitated and this version of the notation looks easier to teach.

Nicol Bolas

unread,
Jul 5, 2018, 9:29:35 AM7/5/18
to ISO C++ Standard - Future Proposals
On Thursday, July 5, 2018 at 3:54:32 AM UTC-4, Ville Voutilainen wrote:
On 4 July 2018 at 04:48, Nicol Bolas <jmck...@gmail.com> wrote:
> Also, we get to use `ConceptName auto/typename/template` when constraining
> explicit template parameters. This unpleasantness is mitigated because

There is no 'ConceptName typename' nor 'ConceptName template' in that proposal.

... I could have sworn I saw something about that. Maybe I got confused by the block of standardese in Part 3.

Ville Voutilainen

unread,
Jul 5, 2018, 11:36:07 AM7/5/18
to ISO C++ Standard - Future Proposals
Chances are that you looked at
http://open-std.org/JTC1/SC22/WG21/docs/papers/2018/p1142r0.html
which is rumination material for some parts of P1141.

Zhihao Yuan

unread,
Jul 6, 2018, 8:25:43 PM7/6/18
to std-pr...@isocpp.org
> From: Ville Voutilainen <ville.vo...@gmail.com>
> Sent: Thursday, July 5, 2018 2:55 AM
>
> > Yes, I would have preferred Herb's syntax, because it means I don't
> > have to use `decltype`, but I can live with this.
>
> The biggest reason for this paper was to try and find an approach that more
> people can live with.
>

This is the piece I dislike the most. A wholesome proposal
must base on real code and real demand. So far, neither
Ranges nor Text_view has any demand for anything what
P1141R0 proposes. It looks like at this point we are not
just designing by committee, but also designing for
committee.

Nicol Bolas

unread,
Jul 6, 2018, 11:03:39 PM7/6/18
to ISO C++ Standard - Future Proposals, z...@miator.net
On Friday, July 6, 2018 at 8:25:43 PM UTC-4, Zhihao Yuan wrote:
> From: Ville Voutilainen <ville.vo...@gmail.com>
> Sent: Thursday, July 5, 2018 2:55 AM
>
> > Yes, I would have preferred Herb's syntax, because it means I don't
> > have to use `decltype`, but I can live with this.
>
> The biggest reason for this paper was to try and find an approach that more
> people can live with.
>

This is the piece I dislike the most.  A wholesome proposal
must base on real code and real demand.  So far, neither
Ranges nor Text_view has any demand for anything what
P1141R0 proposes.  It looks like at this point we are not
just designing by committee, but also designing for
committee.

... huh?

Pretty much everyone agrees that having terse syntax of some form is a good thing. Real demand for terse syntax of some form is undeniable; just ask actual users whether they want it or not.The only question which remains is how you spell it. The Concepts TS had one spelling, but it had some issues. This proposal resolves those issues in a way that most people can live with.

It has none of the parsing problems that the prefixed `template` has. It has none of the oddness of Herb's syntax. And it even improves on the Concepts TS syntax by allowing different kinds of deduction to be constrained.

This is how compromise works; this is the committee process working.

Zhihao Yuan

unread,
Jul 7, 2018, 2:23:36 AM7/7/18
to std-pr...@isocpp.org
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Friday, July 6, 2018 10:04 PM
>
>> A wholesome proposal
>> must base on real code and real demand. So far, neither
>> Ranges nor Text_view has any demand for anything what
>> P1141R0 proposes. It looks like at this point we are not
>> just designing by committee, but also designing for
>> committee.
>
> Pretty much everyone agrees that having terse syntax of some form is a good thing. Real demand for terse syntax of some form is undeniable; just ask actual users whether they want it or not.The only question which remains is how you spell it. The Concepts TS had one spelling, but it had some issues. This proposal resolves those issues in a way that most people can live with.
>

"Real demand for terse syntax of some form is undeniable"
!= " Real demand for terse syntax of any form is undeniable."
If the proposed syntax doesn't help Ranges, then where is
the demand? Here is a roughly complete list of the
signatures that can benefit from P1141R0 in Ranges:

destroy_at
next (1 overload out of 4)
prev (1 overload out of 3)
operator==, operator!= of “unreachable”, 4 overloads

One might say that Ranges is complex -- how would he/she
prove it, show us a simple real-world Concept-enabled
library?

> It has none of the parsing problems that the prefixed `template` has. It has none of the oddness of Herb's syntax. And it even improves on the Concepts TS syntax by allowing different kinds of deduction to be constrained.
>

None of these are in the working paper, so none of these is an
improvement to status quo.

--
Zhihao


Nicol Bolas

unread,
Jul 7, 2018, 11:12:51 AM7/7/18
to ISO C++ Standard - Future Proposals, z...@miator.net
On Saturday, July 7, 2018 at 2:23:36 AM UTC-4, Zhihao Yuan wrote:
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Friday, July 6, 2018 10:04 PM
>
>> A wholesome proposal
>> must base on real code and real demand.  So far, neither
>> Ranges nor Text_view has any demand for anything what
>> P1141R0 proposes.  It looks like at this point we are not
>> just designing by committee, but also designing for
>> committee.
>
> Pretty much everyone agrees that having terse syntax of some form is a good thing. Real demand for terse syntax of some form is undeniable; just ask actual users whether they want it or not.The only question which remains is how you spell it. The Concepts TS had one spelling, but it had some issues. This proposal resolves those issues in a way that most people can live with.
>

"Real demand for terse syntax of some form is undeniable"
!= " Real demand for terse syntax of any form is undeniable."
If the proposed syntax doesn't help Ranges, then where is
the demand?

I think the point you're trying to make is that applying the terse syntax to most Range algorithms wouldn't make them shorter. Or at least, not much.

In some cases, the order of parameters directly forbids it. Anytime a function takes a `Proj` and a `Pred` or a similar functor, the `Proj` function parameter comes last in the function argument list, but the `Proj` is always before the `Pred` in the template parameter list. The reason being that lots of users won't need `Proj`, but `Proj` needs to constrain `Pred`.

However, no terse syntax would help in those cases. Not the Concepts TS, not Bjarne's `template` prefix, nor Herb's `{}` syntax. Once the order of function parameters and template parameters has to differ, you lose the ability to use any terse syntax.

So let's skip all of those. That's pretty much all of the algorithms.

Another case is when you need to actually get the name of one of the deduced types. This is actually quite common:

template <Iterator I> constexpr void advance(I& i, difference_type_t<I> n);

That looks like a prime candidate for terse syntax, but what you get is:

constexpr void advance(Iterator auto &i, different_type_t<decltype(i)> n);

That is shorter... by one character.

This shows the strength of Herb's syntax:

constexpr void advance(Iterator{I} &i, difference_type_t<I> n);

That provides at least some reduction in verbosity.

 
Here is a roughly complete list of the
signatures that can benefit from P1141R0 in Ranges:

    destroy_at
    next (1 overload out of 4)
    prev (1 overload out of 3)
    operator==, operator!= of “unreachable”, 4 overloads

One might say that Ranges is complex -- how would he/she
prove it, show us a simple real-world Concept-enabled
library?

On the one hand, I think this is the wrong standard by which to judge terse syntax.

That is, any generic conceptualized library will have complicated relationships between the various parameters. So terse syntax was never going to be viable for such libraries.

The principle users of terse syntax will not be writers of generic libraries, but users of generic libraries. They're useful when you're writing a quick lambda to be passed somewhere. `[](UnsignedInteger auto i) {}` is going to be easier to digest than `[]<UnsignedInteger I> (I i) {...}`. Not massively shorter of course, but still more readable.

The idea with terse syntax is to keep the simple cases simple. Pretty much nothing in the Range TS is a simple case.

On the other hand... the complexity of the Range system I think has really damaged the utility of terse syntax.

Consider the original Concepts-Lite proposal, which gave us a number of seemingly common-place examples of where terse syntax would be used. In particular, `sort` and `merge`. The Range TS definitions of these functions evolved to the point where terse syntax either wasn't possible or wasn't practical (ie: the terse version wouldn't be appreciably shorter).

For example, the Concepts-Lite proposal suggested this for `sort`:

void sort(Cont& c);

Well, in a range world obviously that would be some `Range` concept, but the general structure is the same. What isn't the same is how that now has to be generalized to allow for comparison functors. Using that terse syntax:

void sort(Range &&r, Comp<decltype(r)> comp = std::less<value_type<decltype(r)>{});

Where `Comp` knows how to act on a particular range to extract the value type and detect that the functor provides comparisons against that value type.

And of course, once you add projection-based-comparison, this becomes completely unworkable. Because now you have template parameters in a different order from function parameters.

The point being that the more conceptualized a function becomes, the chance of it being able to use terse syntax of any form approaches 0.

And that suggests a potential problem: laziness. If users of simple cases get used to terse syntax, they may avoid doing things that stop them from being able to use terse syntax. So rather than write out the complex `sort` in ranges, they'd just write `void sort(RandomAccessRange auto &&r, auto comp = std::less<>{}, auto proj = std::identity{});` and just let it go at that.

Wheresas, if you force them to stick that `RandomAccessRange` in a template argument list, and deny them the ability to use `auto` in function parameters, then they have no choice but to spell it out explicitly. And since they're having to write it long-form, they may as well constrain it properly.

mihailn...@gmail.com

unread,
Jul 7, 2018, 4:46:04 PM7/7/18
to ISO C++ Standard - Future Proposals, z...@miator.net
Considering 

class ConcreteType;

is an age-old way to introduce a type, can we extend that to Concepts?

Concept Type;

After all the suggested Concept auto var = something();

is not unlike class ConcreteType car = something();

And taking a note from in-place we could just let auto be a name of a deduced type 

Concept Type var = something();

I am not that a fan of 2-in-one declaration, but it does help with natural syntax

void advance(Iterator I& i, different_type_t<I> n);

I am willing to argue it also looks most natural to the eye as there is a type next to the name.

auto sort(RandomAccessIterator I first
        , Sentinel<I> S last
        , All Comp comp = less<>{}
        , All Proj proj = identity{}) requires Sortable<I, Comp, Proj>;

Zhihao Yuan

unread,
Jul 7, 2018, 5:49:32 PM7/7/18
to Nicol Bolas, ISO C++ Standard - Future Proposals
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Saturday, July 7, 2018 10:13 AM
>
> I think the point you're trying to make is that applying the terse syntax to most Range algorithms wouldn't make them shorter. Or at least, not much.

Not making them simpler, or readable, or conveying more
precise information. From

template <Iterator I>
void advance(I& i, difference_type_t<I> n);

to

void advance(Iterator auto &i, different_type_t<decltype(i)> n);

, the latter is just cryptic.

> In some cases, the order of parameters directly forbids it.
>
> However, no terse syntax would help in those cases. Not the Concepts TS, not Bjarne's `template` prefix, nor Herb's `{}` syntax. Once the order of function parameters and template parameters has to differ, you lose the ability to use any terse syntax.
>
> So let's skip all of those. That's pretty much all of the algorithms.

We need to look at the right problem to find the right solution;
you cannot hold a solution, and tell me my problem doesn't
work. Here is an example, from

template<InputIterator I, Sentinel<I> S, class Proj = identity,
IndirectUnaryPredicate<projected<I, Proj>> Pred>
bool all_of(I first, S last, Pred pred, Proj proj = Proj{});

to

template<InputIterator I, Sentinel<I> S, IndirectUnaryPredicate<I> Pred>
bool all_of(I first, S last, Pred pred);

template<InputIterator I, Sentinel<I> S, ProjectedUnaryPredicate<I> Proj Pred>
bool all_of(I first, S last, Pred pred, Proj proj);

, isn't the latter more readable?

> The idea with terse syntax is to keep the simple cases simple. Pretty much nothing in the Range TS is a simple case.
>
> On the other hand... the complexity of the Range system I think has really damaged the utility of terse syntax.
> [...]
> The point being that the more conceptualized a function becomes, the chance of it being able to use terse syntax of any form approaches 0.

I don't care a bit what terse syntax can do; I care only what
problem we can solve.

> The principal users of terse syntax will not be writers of generic libraries, but users of generic libraries.

That's an interesting way to formulate the problem, but before
I calling it hypothetical --

> And that suggests a potential problem: laziness. If users of simple cases get used to terse syntax, they may avoid doing things that stop them from being able to use terse syntax. So rather than write out the complex `sort` in ranges, they'd just write `void sort(RandomAccessRange auto &&r, auto comp = std::less<>{}, auto proj = std::identity{});` and just let it go at that.

> Whereas, if you force them to stick that `RandomAccessRange` in a template argument list, and deny them the ability to use `auto` in function parameters, then they have no choice but to spell it out explicitly. And since they're having to write it long-form, they may as well constrain it properly.

, you may as well find it's hard to defend.

> They're useful when you're writing a quick lambda to be passed somewhere. `[](UnsignedInteger auto i) {}` is going to be easier to digest than `[]<UnsignedInteger I> (I i) {...}`. Not massively shorter of course, but still more readable.

If I'm told that from

[]<typename I> (I i)

to

[](typename auto i)

is an simplification I will be very mad, so how the
following transition can be better?

[]<UnsignedInteger I> (I i) --->
[](UnsignedInteger auto i)

Nicol Bolas

unread,
Jul 7, 2018, 6:52:10 PM7/7/18
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, z...@miator.net
On Saturday, July 7, 2018 at 5:49:32 PM UTC-4, Zhihao Yuan wrote:
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Saturday, July 7, 2018 10:13 AM
>
> I think the point you're trying to make is that applying the terse syntax to most Range algorithms wouldn't make them shorter. Or at least, not much.

Not making them simpler, or readable, or conveying more
precise information.  From

    template <Iterator I>
    void advance(I& i, difference_type_t<I> n);

to

    void advance(Iterator auto &i, different_type_t<decltype(i)> n);

, the latter is just cryptic.

I'd hardly call it "cryptic". It's very clear what it's doing, once you understand the syntax. A lambda could be considered "cryptic" if you've never seen the syntax before.

Also, it's not like we don't see this sort of thing with lambdas today: `[](auto &i, difference_type_t<decltype(i)> n);`. Now yes, with C++20 allowing template header syntax in lambdas, we may not need it anymore, but this idiom is hardly unfamiliar to users.

> In some cases, the order of parameters directly forbids it.
>
> However, no terse syntax would help in those cases. Not the Concepts TS, not Bjarne's `template` prefix, nor Herb's `{}` syntax. Once the order of function parameters and template parameters has to differ, you lose the ability to use any terse syntax.
>
> So let's skip all of those. That's pretty much all of the algorithms.

We need to look at the right problem to find the right solution;
you cannot hold a solution, and tell me my problem doesn't
work.

But nobody said it's supposed to be a solution to those problems.

Here is an example, from

    template<InputIterator I, Sentinel<I> S, class Proj = identity,
    IndirectUnaryPredicate<projected<I, Proj>> Pred>
    bool all_of(I first, S last, Pred pred, Proj proj = Proj{});

to

    template<InputIterator I, Sentinel<I> S, IndirectUnaryPredicate<I> Pred>
    bool all_of(I first, S last, Pred pred);

    template<InputIterator I, Sentinel<I> S, ProjectedUnaryPredicate<I> Proj Pred>
    bool all_of(I first, S last, Pred pred, Proj proj);

, isn't the latter more readable?

Um... no? I don't know what that third template argument is supposed to be doing. Are Proj and Pred both types? Are they the same type? Different types? How does that work?

> The idea with terse syntax is to keep the simple cases simple. Pretty much nothing in the Range TS is a simple case.
>
> On the other hand... the complexity of the Range system I think has really damaged the utility of terse syntax.
> [...]
> The point being that the more conceptualized a function becomes, the chance of it being able to use terse syntax of any form approaches 0.

I don't care a bit what terse syntax can do; I care only what
problem we can solve.

Terse syntax is syntactic sugar. By definition, it doesn't solve problems. Sugar makes things taste better; that's all it needs to do.

So the only question is whether the sugar is useful enough to be worth incorporating.

> The principal users of terse syntax will not be writers of generic libraries, but users of generic libraries.

That's an interesting way to formulate the problem, but before
I calling it hypothetical --

> And that suggests a potential problem: laziness. If users of simple cases get used to terse syntax, they may avoid doing things that stop them from being able to use terse syntax. So rather than write out the complex `sort` in ranges, they'd just write `void sort(RandomAccessRange auto &&r, auto comp = std::less<>{}, auto proj = std::identity{});` and just let it go at that.

> Whereas, if you force them to stick that `RandomAccessRange` in a template argument list, and deny them the ability to use `auto` in function parameters, then they have no choice but to spell it out explicitly. And since they're having to write it long-form, they may as well constrain it properly.

, you may as well find it's hard to defend.

> They're useful when you're writing a quick lambda to be passed somewhere. `[](UnsignedInteger auto i) {}` is going to be easier to digest than `[]<UnsignedInteger I> (I i) {...}`. Not massively shorter of course, but still more readable.

If I'm told that from

    []<typename I> (I i)

to

    [](typename auto i)

is an simplification I will be very mad,

The proposed solution would just be `[](auto i)`, just like it is in C++14. Unconstrained is spelled by not using a constraint.

Ville Voutilainen

unread,
Jul 7, 2018, 7:04:53 PM7/7/18
to ISO C++ Standard - Future Proposals, Jason McKesson, Zhihao Yuan
It's better because it moves the constraint closer to the deduction
context. Consider

[]<UnsignedInteger I, Container C, SomethingElse E>(I i, C c, E e);
[](UnsignedInteger auto i, Container auto c, SomethingElse auto e);

Zhihao Yuan

unread,
Jul 8, 2018, 12:05:13 AM7/8/18
to Nicol Bolas, ISO C++ Standard - Future Proposals
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Saturday, July 7, 2018 5:52 PM
>
> Also, it's not like we don't see this sort of thing with lambdas today: `[](auto &i, difference_type_t<decltype(i)> n);`. Now yes, with C++20 allowing template header syntax in lambdas, we may not need it anymore, but this idiom is hardly unfamiliar to users.

I never write that even in C++14, never.

>> Here is an example, from
>>
>> template<InputIterator I, Sentinel<I> S, class Proj = identity,
>> IndirectUnaryPredicate<projected<I, Proj>> Pred>
>> bool all_of(I first, S last, Pred pred, Proj proj = Proj{});
>>
>> to
>>
>> template<InputIterator I, Sentinel<I> S, IndirectUnaryPredicate<I> Pred>
>> bool all_of(I first, S last, Pred pred);
>>
>> template<InputIterator I, Sentinel<I> S, ProjectedUnaryPredicate<I> Proj Pred>
>> bool all_of(I first, S last, Pred pred, Proj proj);
>>
>> , isn't the latter more readable?
>
> Um... no? I don't know what that third template argument is supposed to be doing. Are Proj and Pred both types? Are they the same type? Different types? How does that work?

I had the same feeling when looking at Mergeable{I, S, O}
at the first time, but like you said, "It's very clear what it's
doing, once you understand the syntax." And my point is,
the latter is structurally simpler comparing to the former,
and that's how readability comes.

But comparing to

>> template <Iterator I>
>> void advance(I& i, difference_type_t<I> n);
,
>> void advance(Iterator auto &i, different_type_t<decltype(i)> n);

is structurally more complex and introduces words that
are unrelated to the context, and that's why I called it
cryptic, or you can find a better word for that.

> Terse syntax is syntactic sugar. By definition, it doesn't solve problems. Sugar makes things taste better; that's all it needs to do.

A proposal must solve problems. Lambda is a syntax
sugar and it solves the problem of not able to write
callbacks in expressions. Vittorio's P0915 solves the
problem of unable to express the constraints on
variables. Comparing to

auto item = producer.next();
static_assert(StandardLayoutType<decltype(item)>);
,
auto<StandardLayoutType> item = producer.next();

expresses the idea directly in code. Although I'm a little
surprised that nobody has thought about the most
obvious solution,

StandardLayoutType T;
T item = producer.next();

but I won't say that the proposed solution is not solving
a problem. What problem does P1141R0 solve?

Hubert Tong

unread,
Jul 8, 2018, 12:27:47 AM7/8/18
to std-pr...@isocpp.org, Nicol Bolas
On Sun, Jul 8, 2018 at 12:05 AM, Zhihao Yuan <z...@miator.net> wrote:
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Saturday, July 7, 2018 5:52 PM
>
> Also, it's not like we don't see this sort of thing with lambdas today: `[](auto &i, difference_type_t<decltype(i)> n);`. Now yes, with C++20 allowing template header syntax in lambdas, we may not need it anymore, but this idiom is hardly unfamiliar to users.

I never write that even in C++14, never.

>> Here is an example, from
>>
>>     template<InputIterator I, Sentinel<I> S, class Proj = identity,
>>     IndirectUnaryPredicate<projected<I, Proj>> Pred>
>>     bool all_of(I first, S last, Pred pred, Proj proj = Proj{});
>>
>> to
>>
>>     template<InputIterator I, Sentinel<I> S, IndirectUnaryPredicate<I> Pred>
>>     bool all_of(I first, S last, Pred pred);
>>
>>     template<InputIterator I, Sentinel<I> S, ProjectedUnaryPredicate<I> Proj Pred>
>>     bool all_of(I first, S last, Pred pred, Proj proj);
>>
>> , isn't the latter more readable?
>
> Um... no? I don't know what that third template argument is supposed to be doing. Are Proj and Pred both types? Are they the same type? Different types? How does that work?

I had the same feeling when looking at Mergeable{I, S, O}
at the first time, but like you said, "It's very clear what it's
doing, once you understand the syntax." And my point is,
the latter is structurally simpler comparing to the former,
and that's how readability comes.
Loss of visual anchors can lead to less readability. What's noise to some are important eyecatchers to others.
Unscoped enumerations and anonymous unions come to mind with regards to the Mergeable{I, S, O} syntax.


But comparing to

>>    template <Iterator I>
>>    void advance(I& i, difference_type_t<I> n);
,
>>    void advance(Iterator auto &i, different_type_t<decltype(i)> n);

is structurally more complex and introduces words that
are unrelated to the context, and that's why I called it
cryptic, or you can find a better word for that.

> Terse syntax is syntactic sugar. By definition, it doesn't solve problems. Sugar makes things taste better; that's all it needs to do.

A proposal must solve problems.  Lambda is a syntax
sugar and it solves the problem of not able to write
callbacks in expressions.  Vittorio's P0915 solves the
problem of unable to express the constraints on
variables.  Comparing to

    auto item = producer.next();
    static_assert(StandardLayoutType<decltype(item)>);
,
    auto<StandardLayoutType> item = producer.next();

expresses the idea directly in code.  Although I'm a little
surprised that nobody has thought about the most
obvious solution,

    StandardLayoutType T;
    T item = producer.next();
Probably because it is unclear where T is bound to a concrete type in this syntax.
Is the first binding privileged? Or is this a way of saying that all deductions of T must agree on the deduced type?
Or perhaps T has independent binding on each use?


but I won't say that the proposed solution is not solving
a problem.  What problem does P1141R0 solve?
Wait for it...


auto item = producer.next();
static_assert(StandardLayoutType<decltype(item)>);

StandardLayoutType auto item = producer.next();


--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________


--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/R5J4t8pUi5E/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Zhihao Yuan

unread,
Jul 8, 2018, 1:14:00 AM7/8/18
to std-pr...@isocpp.org, Nicol Bolas
> From: Hubert Tong <hubert.rein...@gmail.com>
> Sent: Saturday, July 7, 2018 11:27 PM
>
> Loss of visual anchors can lead to less readability. What's noise to some are important eyecatchers to others.
> Unscoped enumerations and anonymous unions come to mind with regards to the Mergeable{I, S, O} syntax.

I hope that "Mergeable I S O" is doing better since this
structure is less often seen in the language.

>>
>> StandardLayoutType T;
>> T item = producer.next();
>
> Probably because it is unclear where T is bound to a concrete type in this syntax.
> Is the first binding privileged? Or is this a way of saying that all deductions of T must agree on the deduced type?
Or perhaps T has independent binding on each use?

All deductions of T must agree on the same type, just
like what

template<StandardLayoutType T>
void(T a, T b)

does.

We can even extend this to

EqualityComparable T U;
T x = ...;
U y = ...;

>> What problem does P1141R0 solve?
> Wait for it...
>
> auto item = producer.next();
> static_assert(StandardLayoutType<decltype(item)>);
>
> StandardLayoutType auto item = producer.next();

And I did not comment on that part, right? Of course
there can be a consistency argument to defend the
constraints on parameters, but P0915R0 did not do
that since this argument would be too weak.

Zhihao Yuan

unread,
Jul 8, 2018, 1:19:04 AM7/8/18
to std-pr...@isocpp.org, Jason McKesson
> From: Ville Voutilainen <ville.vo...@gmail.com>
> Sent: Saturday, July 7, 2018 6:05 PM
>
> It's better because it moves the constraint closer to the deduction context.
> Consider
>
> []<UnsignedInteger I, Container C, SomethingElse E>(I i, C c, E e);
> [](UnsignedInteger auto i, Container auto c, SomethingElse auto e);
>

Yes, and I'm not mad after seeing that, but the cost is
a much longer function parameter list plus some "auto"
noises, so I probably won't use it by myself :/

Nicol Bolas

unread,
Jul 8, 2018, 2:23:07 AM7/8/18
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, z...@miator.net
On Sunday, July 8, 2018 at 1:14:00 AM UTC-4, Zhihao Yuan wrote:
> From: Hubert Tong <hubert.rein...@gmail.com>
> Sent: Saturday, July 7, 2018 11:27 PM
>
> Loss of visual anchors can lead to less readability. What's noise to some are important eyecatchers to others.
> Unscoped enumerations and anonymous unions come to mind with regards to the Mergeable{I, S, O} syntax.

I hope that "Mergeable I S O" is doing better since this
structure is less often seen in the language.

It's not seen "less often"; it is seen precisely never. It is completely unlikely any syntax in C++. A general sequence of N tokens, with no separator token between them, is something that appears nowhere in the entire C++ standard.

Oh, and please note that I said "general sequence of". Yes, I know C++ does have places where identifiers follow one another. But when this happens, the grammar restricts how many are around; this is not defined by some language construct. `Identifier Identifier` can declare a variable if `Identifier` is a typename, but `Identifier Identifier Identifier` is always syntactic nonsense. So let's not get bogged down in pedantry here.

Whenever C++ creates a new list of stuff, it always used C-style: there is some kind of token pair bracketing the list (`()` and `{}` in C, `<>` in C++), and each list item is separated from other list items by a comma token. That's how C and C++ work when a list can be arbitrarily long.

A distinction should be made between syntax which is merely unfamiliar and syntax which is alien. What you're talking about is very much the latter.

You may not understand what `Mergeable{I, S, O}` is doing, but at least you know that the given identifiers are associated with Mergeable in some way (since they're in a pair of braces directly after `Mergeable`), and that the tokens that follow the {} are not involved in the same process as the ones inside the `{}`.

>>
>>    StandardLayoutType T;
>>    T item = producer.next();
>
> Probably because it is unclear where T is bound to a concrete type in this syntax.
> Is the first binding privileged? Or is this a way of saying that all deductions of T must agree on the deduced type?
Or perhaps T has independent binding on each use?

All deductions of T must agree on the same type, just
like what

    template<StandardLayoutType T>
    void(T a, T b)

does.

When you write `template<StandardLayoutType T>`, that `T` is a typename. You can use it wherever you could use a typename. You can use it in a function parameter, return type, or just internally in the function. Or never at all.

That is, this is perfectly legal:

template<StandardLayoutType T>
void func(int i = sizeof(T)) {...}

But what you're talking about is not like a template parameter declaration at all. The name you're introducing here cannot be used for anything until it gets used in a deduction context. Only then does it become a typename.

So the analogy you're trying to create between template parameters and this syntax simply doesn't work, because proper template parameters don't have to be deduced at all.

Furthermore, consider your syntax. `T` is utterly useless until it is used in a deduction context, which blesses it with a type. As such, you should combine the declaration of `T` with the deduction context in which it is used. The two should go hand-in-hand, so logically you should do the both at the same time.

But you know why you didn't suggest that. It's because your alien syntax doesn't abide by C++'s conventions on these things, and therefore could never be used in a variable declaration:

StandardLayoutType T next = producer.next();

That's just begging for confusion. Is `next` of type `T`, or does `StandardLayoutType` have two template arguments?

Clarifying which identifiers go with the concept and which are constrained by it would require more C++-like lists. Like:

StandardLayoutType{T} next = producer.next();

Which of course is parseable syntax. And while it may be unfamiliar, it isn't fundamentally alien.

Oh sure, you could do this:

StandardLayoutType T auto next = producer.next();

But really, who wants to do that? P1141 made `auto` not be required for a good reason.


>> What problem does P1141R0 solve?
> Wait for it...
>
> auto item = producer.next();
> static_assert(StandardLayoutType<decltype(item)>);
>
> StandardLayoutType auto item = producer.next();

And I did not comment on that part, right?  Of course
there can be a consistency argument to defend the
constraints on parameters, but P0915R0 did not do
that since this argument would be too weak.

Um, why is it weak? Consistency in a language is not something that should be so idly tossed aside.

On the one hand, we have a proposal that is consistent with itself and consistent with other aspects of C++ (lambda `auto` syntax). And on the other, we have a pair of proposals (your idea and P0915) that both dabble in the same idea, but are not consistent with each other nor with existing aspects of C++.

Why adopt the latter when we can adopt the former? Especially since the former doesn't resolve the non-type template parameter conceptualization issue. That is, `<Concept auto val>` making `val` a value parameter, whose type is constrained by `Concept`.

And I say, take P1141 and add Herb's syntax to it (in deduction contexts, and only optionally). That is, allow me to write: `[](Iterator{I} auto &i, different_type_t<I> n);`. It's the best of both worlds.

By "in deduction contexts", what I mean is anytime you could do `Concept auto ...`, you can also do `Concept{T} auto ...` (in a template parameter list, it does not create a template parameter for this `T`. It just gives it a name). The fact that I could take a specific part of the syntax and add an extension that applies to all uses of that syntax shows just how regular P1141 is compared to your pair of proposals.

Nicol Bolas

unread,
Jul 8, 2018, 3:07:18 AM7/8/18
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, z...@miator.net


On Sunday, July 8, 2018 at 2:23:07 AM UTC-4, Nicol Bolas wrote:
On Sunday, July 8, 2018 at 1:14:00 AM UTC-4, Zhihao Yuan wrote:
> From: Hubert Tong <hubert.rein...@gmail.com>
> Sent: Saturday, July 7, 2018 11:27 PM
>
> Loss of visual anchors can lead to less readability. What's noise to some are important eyecatchers to others.
> Unscoped enumerations and anonymous unions come to mind with regards to the Mergeable{I, S, O} syntax.

I hope that "Mergeable I S O" is doing better since this
structure is less often seen in the language.

It's not seen "less often"; it is seen precisely never. It is completely unlikely any syntax in C++. A general sequence of N tokens, with no separator token between them, is something that appears nowhere in the entire C++ standard.

Oh, and please note that I said "general sequence of". Yes, I know C++ does have places where identifiers follow one another. But when this happens, the grammar restricts how many are around; this is not defined by some language construct. `Identifier Identifier` can declare a variable if `Identifier` is a typename, but `Identifier Identifier Identifier` is always syntactic nonsense. So let's not get bogged down in pedantry here.

Whenever C++ creates a new list of stuff, it always used C-style: there is some kind of token pair bracketing the list (`()` and `{}` in C, `<>` in C++), and each list item is separated from other list items by a comma token. That's how C and C++ work when a list can be arbitrarily long.

Note that member initialization lists in constructors don't have a token pair bracketing them, but they are still comma separated. There was no grammatical necessity for comma separation; after all, each member's initializer has to be in parenthesis (C++11 added braces). So it was theoretically possible to just have a non-separated list.

They didn't do that because... well, that's just not how C and C++ are expected to list things.

Zhihao Yuan

unread,
Jul 8, 2018, 4:09:16 AM7/8/18
to Nicol Bolas, ISO C++ Standard - Future Proposals
> From: Nicol Bolas <jmck...@gmail.com>
> Sent: Sunday, July 8, 2018 1:23 AM
>
>> I hope that "Mergeable I S O" is doing better since this
>> structure is less often seen in the language.
>
> It's not seen "less often"; it is seen precisely never. It is completely unlikely any syntax in C++. A general sequence of N tokens, with no separator token between them, is something that appears nowhere in the entire C++ standard.
> [...]
> A distinction should be made between syntax which is merely unfamiliar and syntax which is alien. What you're talking about is very much the latter.

Looks good to me since alien subsumes unfamiliar.

> That is, this is perfectly legal:
>
> template<StandardLayoutType T>
> void func(int i = sizeof(T)) {...}
>
> But what you're talking about is not like a template parameter declaration at all. The name you're introducing here cannot be used for anything until it gets used in a deduction context. Only then does it become a typename.
>
> So the analogy you're trying to create between template parameters and this syntax simply doesn't work, because proper template parameters don't have to be deduced at all.

Based on the same logic people should not build
analogy between

void foo(auto x);

and

auto x = ...;

because you can do

foo<int>('a');

?

> Furthermore, consider your syntax. `T` is utterly useless until it is used in a deduction context, which blesses it with a type. As such, you should combine the declaration of `T` with the deduction context in which it is used. The two should go hand-in-hand, so logically you should do the both at the same time.

Why they have to? What's the problem with

Copyable T;
int sz = sizeof(T);
T x = foo();

?

> But you know why you didn't suggest that. It's because your alien syntax doesn't abide by C++'s conventions on these things, and therefore could never be used in a variable declaration:
>
> StandardLayoutType T next = producer.next();

I do not know why I didn't suggest that, maybe
because that the idea of putting a type declaration
and a variable declaration in one statement is alien
to me? It's a strawman that I don't even know how
to comment on...

And which part makes

StandardLayoutType T;

an alien syntax? How this is different from

MyIntType X;

?

Both follow C++ declaration style, right?

>> Of course
>> there can be a consistency argument to defend the
>> constraints on parameters, but P0915R0 did not do
>> that since this argument would be too weak.
>
> Um, why is it weak? Consistency in a language is not something that should be so idly tossed aside.

Feature A with a small demand which creates a
consistency argument for feature B with a different
demand is a basic example of camel's nose.

> [...] , we have a pair of proposals (your idea and P0915)

I did not endorse P0915; I only said that it solves a
problem.

"My pair of proposals" would be

template<EqualityComparable T U>
void foo(T a, b a);

and

EqualityComparable T U;
T a = ...;
U b = ...;

> By "in deduction contexts", what I mean is anytime you could do `Concept auto ...`, you can also do `Concept{T} auto ...` (in a template parameter list, it does not create a template parameter for this `T`. It just gives it a name). The fact that I could take a specific part of the syntax and add an extension that applies to all uses of that syntax shows just how regular P1141 is compared to your pair of proposals.

I don't understand the logic behind the "regular"
qualification. My idea is compatible with various
things,

+Status quo: template<IntType a b>
+P1141: template<EqualityComparable T U>
+P1142 if needed: template<TwoContainerLike template V Q>

is that "regular?"

--
Zhihao


Message has been deleted
Message has been deleted

mihailn...@gmail.com

unread,
Jul 8, 2018, 7:28:36 AM7/8/18
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, z...@miator.net
Well, I suggested it above.

It is not confusing, considering we introduce stuff left to right
Class var;
or even
class Class var;

Once we learn Concept Type is "instantiation" of a new type based on a concept we "only" have to deal with two declarations. 

HOWEVER this is not new!

func(class Class& var)

This is double declaration - we introduce both a new type and a variable!

We only tweak the rule

If concept is used instead - the name is still introduced exactly as before, however the type is not a concrete type, but will be late-bound on instantiation! 

It is that simple! No new syntax, just natural evolution of type name introduction - concrete type vs late-bound one.

It is another topic if let instantiation of a type to be undefined until the first use.
Concept Type;
Type var = . . .


 

Clarifying which identifiers go with the concept and which are constrained by it would require more C++-like lists. Like:

StandardLayoutType{T} next = producer.next();

Which of course is parseable syntax. And while it may be unfamiliar, it isn't fundamentally alien.


Not alien, but for the wrong reasons

enum Hi{v} var = …;
class Something{T t;} var = …;

considering we already can omit class (as in class S v;) one will think of Hi{v} as a shorthand declaration
Things are arguably even worse with anonymous structs/enums

Not only that but if we add decorations to the mix things start to get ugly

StandardLayoutType{T}&& next = . . .;
StandardLayoutType{T}* next = . . .;

I looks like we are decorating the Concept. The decoration is barely related to the type

Compare that to the natural

StandardLayoutType T* next = . . .;

Also, lets read this right to left
Variable next, what is next, a pointer, to what, to type T, what is T, a StandardLayoutType

Lets read in-place
Variable next, what is next, a pointer, to what, instantiate StandardLayoutType and declare a type named T constrained by it, the pointer is to T

There is no denying which one is more natural!
And this is not a surprise - we just tweak current rules, no new constructs.  

class T* next = . . .;
Variable next, what is next, a pointer, to what, to type T, what is T, a type

Alberto Barbati

unread,
Jul 9, 2018, 7:11:29 AM7/9/18
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, z...@miator.net, mihailn...@gmail.com
Let's start by saying that I like P1141. I believe that some of the objections I saw in this thread are due to the attempt of using an approach that worked for some of the previously proposed syntaxes. However, I believe the syntax proposed in P1141 and, in particular, the consistent of "auto", allows for a completely different approach, that not only addresses most use-cases but also allows new programming techniques. Allow me to explain my idea that I call "placeholder type alias". The idea is simple: in every place I can write auto, I can write auto{Type} and have Type being defined as an alias of whatever type has been deduced by auto. This means:

In templates:

    void f(auto{T} x) // equivalent to template <class T> void f(T x);

    void f(auto{T} x, T y) // equivalent to template <class T> void f(T x, T y)

    void f(const auto{T}& x) // equivalent to template <class T> void f(const T& x)

    void advance(auto{I}& i, difference_type_t<I> n);

    template <Constraint auto{T} N> void f(); // T is an alias for decltype(N)

    template <auto{T} N> requires /* complex constraint involving T */ void f();

In variable declarations:

   auto{T} x = f(); // T is alias for decltype(x)

In return value:

   auto{Result} f() -> /* complex type declarator */; // Result is an alias for complex type declarator

in this case, Result would be declared right after the trailing-return-type, so it is avaiable in the requires-clause of the function, as well as inside the function body. We should require a trailing-return-type, though, to avoid getting into a catch-22 situation where the type-alias deduction depends on the type-alias itself.

In operators:

    operator Sortable auto{Result}() { /* Result is an alias of the return type */ }

With the same rationale of P1141 I would leave structured bindings alone. The same syntax could be extended to decltype(auto), where applicable.

Comments?
Reply all
Reply to author
Forward
0 new messages