Question about P0121R0 (Concepts TS)

428 views
Skip to first unread message

Andrei L

unread,
Nov 23, 2015, 11:27:37 PM11/23/15
to ISO C++ Standard - Future Proposals
Hello,

I've been reading through the paper and noticed this:

<..> An abbreviated function template is equivalent to a function template (14.6.6) whose template-parameter-list includes one invented template-parameter for each occurrence of a placeholder in the parameter-declaration-clause, in order of appearance <..>

Is this means that in such code:

template <typename T>
concept bool R = CheckSomething<T>;

auto foo(R a, R b) { /* ... */ }

parameters `a' and `b' have the same type? And if so, why? What is the rationale behind that?

Nicol Bolas

unread,
Nov 23, 2015, 11:45:08 PM11/23/15
to ISO C++ Standard - Future Proposals

Because the meaning of that function can be one of two things: either `a` and `b` must have the same type, or they may not. And there are valid use cases where users will want one or the other.

You can always use full template syntax to express either one:

template<R T1, R T2> auto different(T1 t1, T2 t2);

template<R T> auto same(T t1, T t2);

But for shortened syntax, you have to pick one. And there are two criteria for which to prefer:

1) Which one will users find more useful?

2) Which one will be the least confusing?

If you see the same typename in a parameter list, it's the same type. So it would be very confusing to have something that looks like a typename, but doesn't quite act like one. That is, the behavior they choose is the least surprising to users.

Zhihao Yuan

unread,
Nov 24, 2015, 12:09:42 AM11/24/15
to std-pr...@isocpp.org
On Mon, Nov 23, 2015 at 10:45 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
>
> 1) Which one will users find more useful?
>
> 2) Which one will be the least confusing?

My list for these has one more item -- "Which one causes less trouble?"
For all functions accepting >1 arguments utilizing hierarchy concepts
(where you need tag dispatching today, like STL iterators), you cannot
use terse syntax as specified today; for all functions perfect forwarding
>1 constrained arguments of the same concept, you cannot use terse
syntax as specified today (because C&& cannot be T& and T&& at the
same time). The designers know these, but they don't think it's worthy
to drop the same type requirement.

And I understand that there is no way you can convince all people
about whether a "preference" is right or wrong.

So I come with a conservative idea: how about a sign to opt-out such
a requirement:

auto f(R a, R' b); // a single quote after the concept, reads "R prime"
// you can have R'', R''', etc.; the number of ' has no limit, but you won't
// overuse it, right?

--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
___________________________________________________
4BSD -- http://bit.ly/blog4bsd

Andrei L

unread,
Nov 24, 2015, 4:19:06 AM11/24/15
to std-pr...@isocpp.org
Well, that is exactly what i thought about reason to make it this way. But in my opinion, this is clearly wrong.


If you see the same typename in a parameter list, it's the same type.

Agreed.


it would be very confusing to have something that looks like a typename, but doesn't quite act like one.

Not agreed.
 
I think, if one knows that there are such things as type constraints, then one knows that they are not types, and what is their purpose. If one doesn't know what is type constraint, one should learn.

And when one learned, will such thing as Callable, or Iterable, or Movable, or any other well named constraint ever look like a type-name? And good IDE with syntax highlight makes it even easier to distinguish what is type and what is not.

It's not a problem at all, because for a new guy, everything looks scary and unclear and confusing.

Also, given this function, where `auto' is a placeholder for a type that is will be deduced,

auto foo(auto a, auto b)

can we strictly say that its parameters are of the same type?

And given this function, where `C' is a constraint,

auto foo(C a, C b)

can we strictly say that its parameters are of the same type?

Kind of, right? `C' and `C', same thing, but isn't that essentially a shorthand for this?

auto foo(C<auto> a, C<auto> b)

Which is reads, as i see, "deduced types for parameters `a' and `b' must meet requirement C". Not, "a and b have the same type which must meet requirement C", there is no notion that `a' and `b' have same type. So, they should not be of the same type.

And we also have variadic templates! Lets take a look

auto foo(C ...args)

And somehow, at least in current GCC implementation, such call is perfectly valid

foo(1, "2", 3.0);

Here we deduce, and there we're not.

We can also think about it this way:

Suppose we wrote a function

auto foo(auto... ps) // types of the parameters will be deduced

And called it this way

foo(1, "2", 3.0) // called foo<int, char[2], double>(1, "2", 3.0)

Then we realized that we need first parameter to have a name

auto foo(auto a1, auto... ps) // types of the parameters still will be deduced

foo(1, "2", 3.0) // called foo<int, char[2], double>(1, "2", 3.0)

And the we've also, given a name to a second parameter (for some reason)

auto foo(auto a1, auto a2, auto... ps) // types of the parameters still will be deduced

foo(1, "2", 3.0) // called foo<int, char[2], double>(1, "2", 3.0)

So, what we did here. We had a function with some set of parameters. Then we've took a couple (from that set), and gave them a names. Is something happened with their types? No.

And now, lets constrain them

auto foo(C a1, C a2, C... ps)

Bam! Somehow `a1' and `a2' must be of the same type (and everything in ps is not). Isn't type constraint supposed to just check a type (one type, one type at one place, not everything around it) to meet requirements?


You can always use full template syntax to express either one:

template<R T1, R T2> auto different(T1 t1, T2 t2);

template<R T> auto same(T t1, T t2);

Yes, and you are forced to always fallback to full template, when you need to allow parameters of different types. But you generally don't care about exact type when you check if type of given argument meets some requirements. That is the purpose of type constraint, to don't care about exact type. If you need exact type, use exact type.


With inverted rules, if i say, "function `same' must take arguments of the same type", then, with help of template-introduction, i can write it like this:

R {T} auto same(T a1, T a2)

Here, no need to use full template syntax.

I could also write this function


R {T} auto same_not_all(T a1, T a2, R a3) // is a1, a2 and a3 here have same type with current rules?

and that one

auto can_be_same(R a1, R a2)

and no `template <blah blah>' is needed.

So, what i want to say,

If we have a template function that accepts argument of any type, and we want it to accept argument of some particular type, we constrain it.
If we have a template function that accepts unknown amount of arguments (parameter pack), and we want it to accept only particular amount, we constrain it.
If we have a function that accepts an int and only valid int for that function is in range from 42 to 4242, we constrain it.

If there is something free, and we need it to be less free, we constrain it.

From wide to narrow, from any to exact.

--

---
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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Vadim Petrochenkov

unread,
Nov 24, 2015, 5:07:45 AM11/24/15
to ISO C++ Standard - Future Proposals
It's a source of endless confusion, I've seen many people asking the same question for the last years.
The main motivation is a shorter notation for functions taking two iterators of the same type - void foo(Iterator begin, Iterator end) or similar, they are considered a common case.
This motivation is pretty unconvincing given the arrival of ranges and Iterator/Sentinel pairs, but the TS authors seem to stand as a wall on defense of this rule. On the last year CppCon talk A. Sutton basically answered something like "Because we work on this for years and know better what you want" on another question "Why the same type?".

Nicol Bolas

unread,
Nov 24, 2015, 12:10:53 PM11/24/15
to ISO C++ Standard - Future Proposals


On Tuesday, November 24, 2015 at 4:19:06 AM UTC-5, Andrei L wrote:
Well, that is exactly what i thought about reason to make it this way. But in my opinion, this is clearly wrong.

If you see the same typename in a parameter list, it's the same type.

Agreed.

it would be very confusing to have something that looks like a typename, but doesn't quite act like one.

Not agreed.
 
I think, if one knows that there are such things as type constraints,

Well, that right there causes the confusion.

In my mind, the point of terse template function syntax is that the user doesn't care if it's a type constraint or not. That you could take `auto foo(T t1, T t2)` and turn it into `auto foo(C t1, C t2)` (where C is a concept that matches T), and the only visible difference (besides which types are allowed) is that you can't get a function pointer to it. As far as the user is concerned, it works just like `int` or `vector<T>`. It looks like a typename and it behaves like a typename.

If the user has to "know that there are such things as type constraints", then I would say the syntax isn't doing its job.

Nicol Bolas

unread,
Nov 24, 2015, 12:15:40 PM11/24/15
to ISO C++ Standard - Future Proposals
On Tuesday, November 24, 2015 at 5:07:45 AM UTC-5, Vadim Petrochenkov wrote:
It's a source of endless confusion, I've seen many people asking the same question for the last years.

And I'm sure there would be just as much confusion if it were the other way around. If you're going to have terse syntax, you have to pick some answer. And there will be people who want/expect the other way to be the default.

Personally, I like that answer, but I'm against terse syntax altogether...
 
The main motivation is a shorter notation for functions taking two iterators of the same type - void foo(Iterator begin, Iterator end) or similar, they are considered a common case.

They're not "considered" a common case; in the absence of the Ranges TS, it is a common case.

This motivation is pretty unconvincing given the arrival of ranges and Iterator/Sentinel pairs,

Iterator/Sentinel pairs don't use the same concept, so this rule will in no way inhibit the ability of users to use terse syntax for ranges.

The motivation is still there because users will need to be able to have multiple parameters of the same type. Iterators are just a prominent example of this.

Nevin Liber

unread,
Nov 24, 2015, 2:07:11 PM11/24/15
to std-pr...@isocpp.org
On 24 November 2015 at 04:07, Vadim Petrochenkov <vadim.pet...@gmail.com> wrote:
It's a source of endless confusion, I've seen many people asking the same question for the last years.
The main motivation is a shorter notation for functions taking two iterators of the same type - void foo(Iterator begin, Iterator end) or similar, they are considered a common case.

While I don't consider that particularly motivating (because in most of those algorithms begin..end is a range and end is usually just a sentinel which does not need to be the same type as begin; typically the only operation performed on it is equality comparing it with an iterator that has the same type as begin, unless it is a random access iterator, in which case one might calculate end - begin), at the end of the day, we have to pick whether the terse syntax should under constrain (by allowing them to be two different types) or over constrain (by requiring them to be the same type).  While I personally prefer the former, it isn't a strong enough preference to push any farther than bringing it up with those immersed in getting Concepts out the door (which I did a while back).

That being said, I do find Zhihao's idea intriguing...

--
 Nevin ":-)" Liber  <mailto:ne...@cplusplusguy.com+1-847-691-1404

Andrew Tomazos

unread,
Nov 24, 2015, 3:43:55 PM11/24/15
to std-pr...@isocpp.org
On Tue, Nov 24, 2015 at 6:09 AM, Zhihao Yuan <z...@miator.net> wrote:
So I come with a conservative idea: how about a sign to opt-out such
a requirement:

  auto f(R a, R' b);  // a single quote after the concept, reads "R prime"
  // you can have R'', R''', etc.; the number of ' has no limit, but you won't
  // overuse it, right?
 
I was thinking something similar, but using indexes somehow:

auto f(R.0 a, R.1 b)

Casey Carter

unread,
Nov 24, 2015, 3:48:18 PM11/24/15
to ISO C++ Standard - Future Proposals, ne...@cplusplusguy.com
On Tuesday, November 24, 2015 at 1:07:11 PM UTC-6, Nevin Liber wrote:
at the end of the day, we have to pick whether the terse syntax should under constrain (by allowing them to be two different types) or over constrain (by requiring them to be the same type).  While I personally prefer the former, it isn't a strong enough preference to push any farther than bringing it up with those immersed in getting Concepts out the door (which I did a while back).


This exactly. There are reasons both for and against any particular set of semantics for terse syntax. Either we pick one, and usages that would be simpler to express with another use the longer syntax, or we pick none and all usages use the long syntax. The committee deliberated and decided it was better to pick one than zero. Honestly it makes little difference, since the majority of usages need to refer to the parameter types either in the return type or in a requires clause. When that's the case it's preferable to use the longer syntax and have reasonable names for parameter types than use the terse syntax and sprinkle decltype(foo) and decltype(bar) everywhere.

Ville Voutilainen

unread,
Nov 24, 2015, 3:51:56 PM11/24/15
to ISO C++ Standard - Future Proposals
At this point I must advise caution against getting ahead of design
rationale with
outlandish syntax suggestions.

Andrew Tomazos

unread,
Nov 24, 2015, 3:54:59 PM11/24/15
to std-pr...@isocpp.org
On Tue, Nov 24, 2015 at 6:10 PM, Nicol Bolas <jmck...@gmail.com> wrote:
In my mind, the point of terse template function syntax is that the user doesn't care if it's a type constraint or not. That you could take `auto foo(T t1, T t2)` and turn it into `auto foo(C t1, C t2)` (where C is a concept that matches T), and the only visible difference (besides which types are allowed) is that you can't get a function pointer to it. As far as the user is concerned, it works just like `int` or `vector<T>`. It looks like a typename and it behaves like a typename.
 
If we think of a concept as a compile-time interface (where a pure virtual class is a run-time interface), then we would expect:

   class Animal { ... };
   class Dog : Animal { ... };
   class Cat : Animal { ... };

   void f(const Animal& a, const Animal& b);

   Dog dog;
   Cat cat;

   f(dog, cat);

This argues for allowing different types.

However, as others have pointed out there is a long form for expressing either option, so the short-hand default isn't as important.  Concepts has been bouncing around in design phase for many many years so at some point we need to hold our nose and get fracking v1 out the door.

It will ship in GCC 6.0 and implementation is partially completed in Clang too.  Don't know about MSVC or EDG, they gave me the silent treatment.

Thiago Macieira

unread,
Nov 24, 2015, 5:32:23 PM11/24/15
to std-pr...@isocpp.org
Whichever way you choose, you're going to disappoint someone.

I've seen arguments for and against specifying the same type and it's easy to
argue for the other side. One example for both cases:

compare(InputIterator begin1, InputIterator end1, InputIterator begin2)

Here, begin2 does not need to have the same type as begin1 and end1. In fact,
one could argue that even begin1 and end1 need not be the same type, so long
as they're comparable to one another. But the most common case is that begin1
and end1 have the same type.

In fact, the requirement that begin1 and end1 be comparable should be part of
the concept signature, which means a function that would accept two different
iterator types would need to be:

compare(InputIterator begin1,
InputIteratorComparableTo<decltype(begin1)> end1,
InputIterator begin2)

And since the second iterator has a different declaration from the first, this
cannot be used as an argument for supporting the case where they need to be
different.

Anyway, maybe we should look into the principle of least surprise. Which case
would cause most surprise if used incorrectly?

a) the case where an API constrains the types more than it should
b) the case where an API fails to constrain the types as much as it should

In case a, the misuse would result in the compiler failing to compile and
reporting the reason why the concepts failed to match. In case b, the compiler
would accept the overload, proceed to instantiate the template, and possibly
run into an error later where the assumption failed.

Given that one of the stated goals of Concepts is to give us better error
messages because types are checked against the concept prior to the overload
selection, I'd say that the biggest surprise is case b.

That would support the solution as it is today: the same concept name used
more than once means the same type.

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center
PGP/GPG: 0x6EF45358; fingerprint:
E067 918B B660 DBD1 105C 966C 33F5 F005 6EF4 5358

Thiago Macieira

unread,
Nov 24, 2015, 5:40:52 PM11/24/15
to std-pr...@isocpp.org
On Tuesday 24 November 2015 21:54:57 Andrew Tomazos wrote:
> On Tue, Nov 24, 2015 at 6:10 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> > In my mind, the point of terse template function syntax is that the user
> > *doesn't care* if it's a type constraint or not. That you could take
> > `auto foo(T t1, T t2)` and turn it into `auto foo(C t1, C t2)` (where C
> > is a concept that matches T), and the only visible difference (besides
> > which types are allowed) is that you can't get a function pointer to it.
> > As far as the user is concerned, it works just like `int` or `vector<T>`.
> > It looks like a typename and it behaves like a typename.
>
> If we think of a concept as a compile-time interface (where a pure virtual
> class is a run-time interface), then we would expect:
>
> class Animal { ... };
> class Dog : Animal { ... };
> class Cat : Animal { ... };
>
> void f(const Animal& a, const Animal& b);
>
> Dog dog;
> Cat cat;
>
> f(dog, cat);
>
> This argues for allowing different types.

That's an example of polymorphism but it's showing the same type: Animal. So I
don't agree with you that it argues for allowing different types.

It would argue for different type if you could compile:

class Shape {};
class Triangle : Shape {};
Triangle triangle;
f(dog, triangle);

And that's not the case.

Concepts are not base classes. Polymorphism implies that there are at least
two types in consideration: the interface type and the concrete type. You can
declare:

Animal *ptr = new Dog;
ptr = new Cat;

But you cannot do that for a concept:

InputIterator it = std::string::const_iterator{};
it = std::vector<int>::const_iterator{};

Bengt Gustafsson

unread,
Nov 24, 2015, 6:01:52 PM11/24/15
to ISO C++ Standard - Future Proposals
There is another aspect of this apart from which use case is more common, or intuitive: How hard is it to express the other possible intent?

If a concept name means the same type for all parameters we need to do this to declare the compare function mentioned above:

template<InputIterator I1, InputIterator I2> bool compare(I1 begin1, I1 end1, I2 begin2); 

If each occurance of the InputIterator concept is separate we can still write:

bool compare(InputIterator begin1, decltype(begin1) end1, InputIterator begin2);

I thinks this speaks at least a little for allowing different types, as the more limited requirement can still be expressed within the terse syntax.

Another issue regards the use of auto as the "catch all" type which works according to the "separate paradigm:

For a generic lambda, the closure type has a public inline function call operator member template (14.5.2) whose template-parameter-list consists of one invented type template-parameter for each occurrence of auto in the lambda’s parameter-declaration-clause, in order of appearance

To select the other option for limited template parameters seems to creat anothre inconsistency.

I always considered the requirement that all variables declared in an auto variable declaration have the same base type a mistake. Who can come up with a case when reversing this decision breaks existing code. I tried this:

auto a=1, b=2.0;

which would conceivably change b from being an int initialized with a double to a double, but the compiler didn't like that.

Apart from the quite obvious nicety of being able to write the above declaration my main motivation was that for-init statements like this would work naturally:

for (auto iter = vec.begin(), ix = 0; iter != vec.end(), iter++, ix++)
    ....

Superficially this ship has long since sailed but if noone can find a use case preventing the reversal of the decision we could possibly unify all uses of auto and concepts if we go in the direction of separating each use.

Andrei L

unread,
Nov 24, 2015, 6:14:11 PM11/24/15
to std-pr...@isocpp.org
One example for both cases:
 
compare(InputIterator begin1, InputIterator end1, InputIterator begin2)
 
Here, begin2 does not need to have the same type as begin1 and end1. In fact,
one could argue that even begin1 and end1 need not be the same type, so long
as they're comparable to one another. But the most common case is that begin1
and end1 have the same type.

template <typename T>
concept bool InputIterator = Iterator<I> && requires (I i) { /* ... */ };

InputIterator {I} bool compare(I begin1, I end1, InputIterator begin2)

How about that one? Both are Iterators, both are comparable. begin2 not required to be the same.


In fact, the requirement that begin1 and end1 be comparable should be part of
the concept signature, which means a function that would accept two different
iterator types would need to be:

        compare(InputIterator begin1,
                InputIteratorComparableTo<decltype(begin1)> end1,
                InputIterator begin2)

And since the second iterator has a different declaration from the first, this
cannot be used as an argument for supporting the case where they need to be
different.

template <typename I, typename S>
concept bool InputIterator_Sentinel = InputIterator<I> && Iterator_Sentinel<I, S>

InputIterator_Sentinel {I, S} bool compare(I begin1, S end1, InputIterator begin2)

Comparable, and not required to be the same.

Easy enough, i think.

T. C.

unread,
Nov 24, 2015, 6:31:10 PM11/24/15
to ISO C++ Standard - Future Proposals


On Tuesday, November 24, 2015 at 6:01:52 PM UTC-5, Bengt Gustafsson wrote:

If each occurance of the InputIterator concept is separate we can still write:

bool compare(InputIterator begin1, decltype(begin1) end1, InputIterator begin2);

 
This is not equivalent to

template<InputIterator I1, InputIterator I2> bool compare(I1 begin1, I1 end1, I2 begin2);

decltype() is a non-deduced context, meaning that the first version accepts all end1's that can be converted to begin1's type. The second one deduces I1 independently from both arguments and requires that they match.

Thiago Macieira

unread,
Nov 24, 2015, 6:49:48 PM11/24/15
to std-pr...@isocpp.org
On Wednesday 25 November 2015 04:14:09 Andrei L wrote:
> > And since the second iterator has a different declaration from the first,
> > this
> > cannot be used as an argument for supporting the case where they need to
> > be
> > different.
>
> template <typename I, typename S>
> concept bool InputIterator_Sentinel = InputIterator<I> &&
> Iterator_Sentinel<I, S>
>
> InputIterator_Sentinel {I, S} bool compare(I begin1, S end1, InputIterator
> begin2)
>
> Comparable, and not required to be the same.
>
> Easy enough, i think.

Indeed.

But what I said above still applies: since you used a different declaration,
it does not support the argument that two InputIterator uses in the same
declaration should be different.

Andrei L

unread,
Nov 24, 2015, 9:53:59 PM11/24/15
to std-pr...@isocpp.org
But what I said above still applies: since you used a different declaration,
it does not support the argument that two InputIterator uses in the same
declaration should be different.

I'm not making an argument that two iterators should be different. Also, i am not talking about STL's "common case" which is not common, but the only possible. And let's not forget about RangesTS, and STL2, where that "common case" is not so common.

I'm saying that type checking has nothing to do with defining parameters of a function. It's just none of its business. User defines parameters. User defines their types. User decides, if they are of the same type or not.

merge(Container src_a, Container src_b, Container output)

Do i want `src_a', `src_b' and `output' be of the same type? No. - But, because `Container' placed where types are placed, someone might think that it's a type. That is confusing for that someone, lets make types the same. - Okay, but what is about ones who is not confused, who actually learned the language? Why make them pay and suffer? Is ones who don't learned prioritized over ones who learned?

And how is that even possible that someone will try to use a function like above, without actually knowing what is a `Container'? If you have a docs, you'll read that `merge' is a template function that accepts three objects, types of which meet a requirements of the `Container' concept. And if you doesn't have a docs you will go to source code and see for yourself that `Container' is not a type.

I say, there is no source of confusion. Right now, confusion is not in the heads of a users.

Oh, and that terse syntax hides that it's a template function. - But that is not a problem. It is purpose of that syntax, to get rid of template boilerplate, no? And if that is a problem, why introduce it in the first place? Why ever introduce new, actually simplified syntax? What for? We can use current.

Which case would cause most surprise if used incorrectly?
 
a) the case where an API constrains the types more than it should
b) the case where an API fails to constrain the types as much as it should

Who writes an API? A human. It so happens that people make mistakes. So the second case is not surprising at all.

On the other hand, if i read a sentence and i see what is happening here, what words are used, and what they mean (what they actually mean and not some meaning invented from nothing), and then someone says that completely different things happening, that is where confusion will rise.

you used a different declaration

I used different declaration to show that i don't need InputIterator to force same type for begin and end, if i need same types i define them to be the same. Just like i do right now, by the way, by writing

template <typename I, typename O>
O copy(I begin, I end, O out)

I don't need obscure rules to define begin and end to be of the same type. I can use template-introduction for that, which is perfectly suitable. And by allowing constrained parameters with same constraints be of different types, it's will be also easier to write functions where you just doesn't care about actual types. And if it's not enough, you always have usual template syntax, for both cases. Cake for you, and cake for you. Everyone is happy.

Andrei L

unread,
Nov 24, 2015, 9:56:13 PM11/24/15
to std-pr...@isocpp.org
Given that one of the stated goals of Concepts is to give us better error
messages because types are checked against the concept prior to the overload
selection

Error messages.. I've seen some beginners asking programming questions, and you know what? Some of them can't even understand what compiler says about that missed semicolon at the end of line. That's where great confusion happens.

Nicol Bolas

unread,
Nov 25, 2015, 2:07:13 AM11/25/15
to ISO C++ Standard - Future Proposals
On Tuesday, November 24, 2015 at 9:53:59 PM UTC-5, Andrei L wrote:
But what I said above still applies: since you used a different declaration,
it does not support the argument that two InputIterator uses in the same
declaration should be different.

I'm not making an argument that two iterators should be different. Also, i am not talking about STL's "common case" which is not common, but the only possible. And let's not forget about RangesTS, and STL2, where that "common case" is not so common.

I'm saying that type checking has nothing to do with defining parameters of a function. It's just none of its business. User defines parameters. User defines their types. User decides, if they are of the same type or not.

merge(Container src_a, Container src_b, Container output)

Do i want `src_a', `src_b' and `output' be of the same type? No.

I would say that your `merge` function here is underconstrained. Why? Because even if those different `Container` instances could deduce different types, those three different types need to have something in common. Namely, that the value type from `src_a` and `src_b` are convertible to the value type for `output`.

So if you actually applied all of the constraints that your function needs, you couldn't use terse syntax. So this is not an example of the problem.

And I think that leads to an interesting question. How often do you have a function which:

1) Takes two arguments that can be of different types.

2) Those two arguments are constrained by the same concept.

3) There are no additional constraints on those types that would prevent you from using terse syntax.

Take your suggestion for `compare`. The first range and second range need not be the same types. But, they do need to have comparable value types. And a properly constrained template function will apply that constraint, so that users who pass the wrong things will get reasonable errors. And you can't apply such a constraint with terse syntax.

I'd bet that 90% of the time that #1 and #2 are true, there's probably some additional constraint that your function requires of those two types.

- But, because `Container' placed where types are placed, someone might think that it's a type. That is confusing for that someone, lets make types the same. - Okay, but what is about ones who is not confused, who actually learned the language? Why make them pay and suffer? Is ones who don't learned prioritized over ones who learned?

Why make the language more confusing to users? Why make people have to learn esoteric rules?

Somebody will be inconvenienced no matter what you do. Why is your side the one that deserves to have the special syntax? Indeed, you seem to argue exactly why they shouldn't: because they're the ones who are willing to learn and use arcane rules. And therefore, they will be less inconvenienced by such arcane rules than if you do it the other way around.

And how is that even possible that someone will try to use a function like above, without actually knowing what is a `Container'?

The same way that people use std::vector without knowing that the type it takes actually has minimum requirements.
 
If you have a docs, you'll read that `merge' is a template function that accepts three objects, types of which meet a requirements of the `Container' concept. And if you doesn't have a docs you will go to source code and see for yourself that `Container' is not a type.

I say, there is no source of confusion. Right now, confusion is not in the heads of a users.

Oh, and that terse syntax hides that it's a template function. - But that is not a problem. It is purpose of that syntax, to get rid of template boilerplate, no?

The purpose of terse syntax is to minimize template boilerplate in the most common and useful of cases. It is not intended to be a full-fledged replacement for declaring template functions. There will always be cases where you need to use full template syntax.

The question is why should your case be the one that gets favoritism?
 
I used different declaration to show that i don't need InputIterator to force same type for begin and end, if i need same types i define them to be the same. Just like i do right now, by the way, by writing

template <typename I, typename O>
O copy(I begin, I end, O out)

I don't need obscure rules to define begin and end to be of the same type. I can use template-introduction for that, which is perfectly suitable. And by allowing constrained parameters with same constraints be of different types, it's will be also easier to write functions where you just doesn't care about actual types. And if it's not enough, you always have usual template syntax, for both cases. Cake for you, and cake for you. Everyone is happy.

Nobody has contested that full template syntax can do either one. Nobody has suggested that having terse syntax use one way means that the other way becomes impossible.

The question is who gets to use the simple syntax. Simply declaring that the other side can still use full template syntax is basically saying, "why don't you just let us win? After all, if you do, you lose."

Do a lot of people find this kind of argument convincing?

Andrew Tomazos

unread,
Nov 25, 2015, 1:47:35 PM11/25/15
to std-pr...@isocpp.org
On Tue, Nov 24, 2015 at 11:40 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Tuesday 24 November 2015 21:54:57 Andrew Tomazos wrote:
> On Tue, Nov 24, 2015 at 6:10 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> > In my mind, the point of terse template function syntax is that the user
> > *doesn't care* if it's a type constraint or not. That you could take
> > `auto foo(T t1, T t2)` and turn it into `auto foo(C t1, C t2)` (where C
> > is a concept that matches T), and the only visible difference (besides
> > which types are allowed) is that you can't get a function pointer to it.
> > As far as the user is concerned, it works just like `int` or `vector<T>`.
> > It looks like a typename and it behaves like a typename.
>
> If we think of a concept as a compile-time interface (where a pure virtual
> class is a run-time interface), then we would expect:
>
>    class Animal { ... };
>    class Dog : Animal { ... };
>    class Cat : Animal { ... };
>
>    void f(const Animal& a, const Animal& b);
>
>    Dog dog;
>    Cat cat;
>
>    f(dog, cat);
>
> This argues for allowing different types.

That's an example of polymorphism but it's showing the same type: Animal. So I
don't agree with you that it argues for allowing different types.

It's kind of subjective I guess.  The types of the arguments to the function call expression are Dog and Cat.  Likewise in the following:

concept Animal ...;
class Dog { ... };
static_assert(Dog conforms to Animal);
class Cat { ... };
static_assert(Cat conforms to Animal);

void f(Animal a, Animal b);

Dog dog;
Cat cat;

f(dog, cat);

I argue that both this example and the previous are similar in important ways.

The mechanics of derived-to-base conversions, base class subobjects, reference binding, etc, etc - are secondary to how the language should work at a conceptual level.  They are just a means to an end.

Thiago Macieira

unread,
Nov 25, 2015, 2:50:42 PM11/25/15
to std-pr...@isocpp.org
On Wednesday 25 November 2015 19:47:33 Andrew Tomazos wrote:
> concept Animal ...;
> class Dog { ... };
> static_assert(Dog conforms to Animal);
> class Cat { ... };
> static_assert(Cat conforms to Animal);
>
> void f(Animal a, Animal b);
>
> Dog dog;
> Cat cat;
>
> f(dog, cat);
>
> I argue that both this example and the previous are similar in important
> ways.
>
> The mechanics of derived-to-base conversions, base class subobjects,
> reference binding, etc, etc - are secondary to how the language should work
> at a conceptual level. They are just a means to an end.

In this case, the function f would be a template. And that's where things
become blurry, because if the arguments are templates, there's no automatic
casting to a base class.

Andrew Tomazos

unread,
Nov 25, 2015, 3:14:27 PM11/25/15
to std-pr...@isocpp.org
On Wed, Nov 25, 2015 at 8:50 PM, Thiago Macieira <thi...@macieira.org> wrote:
On Wednesday 25 November 2015 19:47:33 Andrew Tomazos wrote:
> concept Animal ...;
> class Dog { ... };
> static_assert(Dog conforms to Animal);
> class Cat { ... };
> static_assert(Cat conforms to Animal);
>
> void f(Animal a, Animal b);
>
> Dog dog;
> Cat cat;
>
> f(dog, cat);
>
> I argue that both this example and the previous are similar in important
> ways.
>
> The mechanics of derived-to-base conversions, base class subobjects,
> reference binding, etc, etc - are secondary to how the language should work
> at a conceptual level.  They are just a means to an end.

In this case, the function f would be a template. And that's where things
become blurry, because if the arguments are templates, there's no automatic
casting to a base class.

Right, instead of derived-to-base conversion there is concept-conforming type deduction.  The former happens at run-time, the later happens at compile-time.  Beyond that, at a high-level, I think they are supposed to be similar.

Vadim Petrochenkov

unread,
Nov 25, 2015, 3:44:07 PM11/25/15
to ISO C++ Standard - Future Proposals
In some languages both concept-like static polymorphism and OOP-style dynamic polymorphism are even served by the same language feature.

Andrei L

unread,
Nov 25, 2015, 6:21:41 PM11/25/15
to std-pr...@isocpp.org
I would say that your `merge` function here is underconstrained. Why? Because even if those different `Container` instances could deduce different types, those three different types need to have something in common. Namely, that the value type from `src_a` and `src_b` are convertible to the value type for `output`.

But, nothing was said about what is a Container, here. Maybe it's enough. Maybe Container, it is something that can hold some type T, which can hold any other type. And maybe that `merge` function is not an analogue of `merge` in STL. And of course, it's not an example of perfectly constrained, function. One, most probably, would need to add additional requires-clause.


So if you actually applied all of the constraints that your function needs, you couldn't use terse syntax.

And I think that leads to an interesting question. How often do you have a function which:

1) Takes two arguments that can be of different types.

2) Those two arguments are constrained by the same concept.

3) There are no additional constraints on those types that would prevent you from using terse syntax.

I'd bet that 90% of the time that #1 and #2 are true, there's probably some additional constraint that your function requires of those two types.
 
Take your suggestion for `compare`. The first range and second range need not be the same types. But, they do need to have comparable value types. And a properly constrained template function will apply that constraint, so that users who pass the wrong things will get reasonable errors. And you can't apply such a constraint with terse syntax.
 
That's why i am unhappy with current situation, i can apply additional constraints.

InputIterator_Sentinel {I, S} bool compare(I begin1, S end1, InputIterator begin2)
  requires EquallyComparable<decltype(*begin1), decltype(*begin2)>
        && /* any additional constraints */

and if you need and actual type of begin2, and you don't want to use decltype, you can use usual template syntax.


Why make the language more confusing to users? Why make people have to learn esoteric rules?

That is why i am here! Why? Rule that is made to make me, a user, less confused, made me confused even before it came into effect!
 
Somebody will be inconvenienced no matter what you do. Why is your side the one that deserves to have the special syntax? Indeed, you seem to argue exactly why they shouldn't: because they're the ones who are willing to learn and use arcane rules. And therefore, they will be less inconvenienced by such arcane rules than if you do it the other way around.

That is not an arcane rule. Again, what concept does (should do)? It checks, that object of type T behaves as you need, not that everything that behaves the same way have the same type.

Ducks, i brought you some amount of ducks. They look like a ducks, they quack like a ducks, and you treat them like a ducks. But then, i take duck consumes off, and you see that this is a cat, this is a dog, and this is a horse! And quacking sounds was made with little speakers! What you will do, put costumes back on, and say, "Nope, they're all ducks"?

And how is that even possible that someone will try to use a function like above, without actually knowing what is a `Container'?

The same way that people use std::vector without knowing that the type it takes actually has minimum requirements.

People now what std::vector is. How it behaves, and how to use it. To call a function F which takes object of type T, you must, know what you can pass to that function, and what you get as a result.

The purpose of terse syntax is to minimize template boilerplate in the most common and useful of cases.

One of the most common and useful of cases:

auto copy(InputIterator begin, InputIterator end, OutputIterator out) -> OutputIterator
  requires /* ... */

Is this how it's written right now in GCC, Clang, MSVC? No (because it is not time to rewrite it, but anyways), in GCC it is like that:

template<typename _II, typename _OI>
    inline _OI
    copy(_II __first, _II __last, _OI __result)
    {
      // concept requirements
      __glibcxx_function_requires(_InputIteratorConcept<_II>)
      __glibcxx_function_requires(_OutputIteratorConcept<_OI,
	    typename iterator_traits<_II>::value_type>)
      __glibcxx_requires_valid_range(__first, __last);

      return (std::__copy_move_a2<__is_move_iterator<_II>::__value>
	      (std::__miter_base(__first), std::__miter_base(__last),
	       __result));
    }

When GCC 6.0 will be released, will this function be made to use terse syntax? Or __glibcxx_* macro will be redefined?

What about Eric Niebler's and Casey Carter's STL2 version?

template <InputIterator I, Sentinel<I> S, WeaklyIncrementable O>
  requires
    models::IndirectlyCopyable<I, O>
  tagged_pair<tag::in(I), tag::out(O)>
  copy(I first, S last, O result)
  {
    for (; first != last; ++first, ++result) {
      *result = *first;
    }
    return {__stl2::move(first), __stl2::move(result)};
  }


It uses concepts already, but, where is a terse syntax here? Is someone going to use it? Where is the most common and useful of cases applies?

STL1 is not the only now, and most probably, it will not use terse syntax. And rule, that i am arguing about, is for terse syntax, and if it is not going to be used, for whom that rule is?

<..> to minimize template boilerplate <..>. It is not intended to be a full-fledged replacement for declaring template functions.

I'm not suggesting to replace it. I'm saying that it could be minimized to the ground.

There will always be cases where you need to use full template syntax.

Not arguing with that.
 
Nobody has contested that full template syntax can do either one. Nobody has suggested that having terse syntax use one way means that the other way becomes impossible.

Suppose you writing a library with a lot of template functions. Some of them take arguments of the same, type. Some of them not. Suppose you want to constrain all you functions properly, so you use terse syntax for parameters with same type and usual template syntax for parameters that share same concept but not required to have same type.

Will you be writing it like that

template <R T2>

auto maybe_same(R p1, T2 p2)

auto same(R p1, R p2)

or for the sake of consistency, like that:


template <R T1, R T2>
auto maybe_same(T1 p1, T2 p2)

template <R T>
auto same(T p1, T p2)

?

Without same-type rule it could be written like that:

auto maybe_same(R p1, R p2)

R {T} auto same(T p1, T p2)

Aaand consistent version looks kinda, better than others.. maybe just more familliar. But first version, i don't like it at all.

Andrei L

unread,
Nov 25, 2015, 7:09:31 PM11/25/15
to std-pr...@isocpp.org
When you writing a function and you make use of type constraints, and you decided to use terse syntax, you think, "How that object (argument) should behave?" First thing you think about - is behavior. And when you choose right behavior, you think, "What about its type? Do i need these be of the same type?", not, "Do i need them to be different?". And you think that way, because you didn't specified their types, because you choose to use terse syntax, and same-type rule is basically saying, "Oh, i see you want them to be of the same type! I'll make it". But no you don't, you don't want, you don't care about type.

Isn't that is how we do it in life? If we care, we take actions, we make statements, that we care, and if we don't, we do nothing. We don't take actions to show that we don't care, because if we do something, that usually means that we care. That is what bothers me.

Andrei L

unread,
Nov 25, 2015, 7:36:38 PM11/25/15
to std-pr...@isocpp.org
Also, since type constraints now could be applied everywhere, i can declare a variable with it.

Container c;

Non-confused user will think that Container is a type. And what he will get if he will try to initialize it?

Container c {1, 2, 3};

Compiler error saying something about `variable of type auto should be initialized with single value`? Or

Container c = {1, 2, 3};

he will end up with std::initializer_list<int>?

Confusion about type constraints is everywhere, and same-type rule is not helping.

tsvetan.d...@gmail.com

unread,
Nov 25, 2015, 8:30:26 PM11/25/15
to ISO C++ Standard - Future Proposals
Not sure if it's relevant but seems that in the latest draft generic lambdas non-constrained parameters are treated in exactly the opposite way - see for example 5.1.2.5:

auto glambda = [](auto a, auto&& b) { return a < b; };
 bool b = glambda(3, 3.14); // OK
 auto vglambda = [](auto printer){
         return [=](auto&& ... ts) { // OK: ts is a function parameter pack
               printer(std::forward(ts)...);
               return [=]() { printer(ts ...);
        };
 }; }; auto p = vglambda( [](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; } ); auto q = p(1, ’a’, 3.14); // OK: outputs 1a3.14 q(); // OK: outputs 1a3.14 
Seems somewhat inconsistent to treat unconstrained types one way and constrained exactly the opposite

Andrei L

unread,
Nov 25, 2015, 8:46:08 PM11/25/15
to std-pr...@isocpp.org
Seems somewhat inconsistent to treat unconstrained types one way and constrained exactly the opposite

Yes, exactly. And given that `Constraint a` is same thing as `Constraint<auto> a`, so type of `a` is still `auto`, inconsistency intensifies.

Nicol Bolas

unread,
Nov 26, 2015, 12:50:46 PM11/26/15
to ISO C++ Standard - Future Proposals
On Wednesday, November 25, 2015 at 6:21:41 PM UTC-5, Andrei L wrote:
So if you actually applied all of the constraints that your function needs, you couldn't use terse syntax.

And I think that leads to an interesting question. How often do you have a function which:

1) Takes two arguments that can be of different types.

2) Those two arguments are constrained by the same concept.

3) There are no additional constraints on those types that would prevent you from using terse syntax.

I'd bet that 90% of the time that #1 and #2 are true, there's probably some additional constraint that your function requires of those two types.
 
Take your suggestion for `compare`. The first range and second range need not be the same types. But, they do need to have comparable value types. And a properly constrained template function will apply that constraint, so that users who pass the wrong things will get reasonable errors. And you can't apply such a constraint with terse syntax.
 
That's why i am unhappy with current situation, i can apply additional constraints.

InputIterator_Sentinel {I, S} bool compare(I begin1, S end1, InputIterator begin2)
  requires EquallyComparable<decltype(*begin1), decltype(*begin2)>
        && /* any additional constraints */

and if you need and actual type of begin2, and you don't want to use decltype, you can use usual template syntax.

Wait... Concepts TS allows that functionality right now. There's nothing that says you can't have a requires clause in addition to terse syntax. Also, I can't find anything that would prevent you from using template introduction syntax with a constrained parameter.

I was wrong about not being able to mix the two/three. Though doing what you did above makes the terse syntax not particularly "terse".

So with the exception of the last statement about `InputIterator` not referring to the type of `begin2`... what exactly are you missing from the current system?

And actually, you bring up a very important point. Namely, if we do things your way, we have to throw around a lot of `decltype(variableName)` to get the typename of a variable if you use terse syntax.

Or more to the point, if I do this:

void foo(Thingy t)
{
 
Thingy j = <expr>;
 
static_assert(is_same<decltype(t), decltype(j)>::value, "Why should this ever be false?");
}

Given absolutely no knowledge of what `Thingy` is, why should any user ever expect that assert to trigger? Or more to the point, if we define terse template syntax the way you want, then users have to know why this may not be true. That sounds very much like a trap, much like the most vexing parse. Knowing why it doesn't work requires knowing way too much about C++ minutiae.

Similarly, why should I be prevented from doing `Thingy{}` to construct a value of the same type as `t`? Why should I have to do `using ThingyType = decltype(t); ThingyType{}` just to create another value of that type?
 
Why make the language more confusing to users? Why make people have to learn esoteric rules?

That is why i am here! Why? Rule that is made to make me, a user, less confused, made me confused even before it came into effect!

Why is your confusion more important than someone else's confusion? Somebody's going to be confused either way. It's either going to be the people who are acutely aware that using a constraint makes the function a template, or it's going to be the people who aren't aware of that.

As far as I'm concerned, the former group can handle it better than the latter.

Somebody will be inconvenienced no matter what you do. Why is your side the one that deserves to have the special syntax? Indeed, you seem to argue exactly why they shouldn't: because they're the ones who are willing to learn and use arcane rules. And therefore, they will be less inconvenienced by such arcane rules than if you do it the other way around.

That is not an arcane rule.

It requires knowing:

1) That you're using a constraint rather than a typename, despite all appearances to the contrary.

2) That the rules for using a constraint in a parameter list are different from the rules of using a typename in a parameter list.

That makes it arcane. It requires knowing way more than most people need to.
 
Again, what concept does (should do)? It checks, that object of type T behaves as you need, not that everything that behaves the same way have the same type.

Ducks, i brought you some amount of ducks. They look like a ducks, they quack like a ducks, and you treat them like a ducks. But then, i take duck consumes off, and you see that this is a cat, this is a dog, and this is a horse! And quacking sounds was made with little speakers! What you will do, put costumes back on, and say, "Nope, they're all ducks"?

You're arguing my case for me.

If the point of putting a duck costume on a cat/dog/horse is that it behave like a duck, then make it behave like a duck. While it's wearing the duck costume, it should be as indistinguishable as possible from a duck. If I have to know whether it's a cat or dog or whatever, then why make it look like a duck at all?

If the point of terse template syntax is to make a constrained template function act like a non-template function, then it should make it act like a non-template function. Constraints used in place of typenames should therefore act like typenames. That is essentially how the Concepts TS makes it work.

I don't much care for terse template syntax, but given the goals of it, this is very much the right choice.

When GCC 6.0 will be released, will this function be made to use terse syntax? Or __glibcxx_* macro will be redefined?

It may switch to it, and it may not. That's entirely up to GCC's implementation of the standard library.

The question is ultimately not whether it would. But where it could. And for this function, it certainly could.

What about Eric Niebler's and Casey Carter's STL2 version?

template <InputIterator I, Sentinel<I> S, WeaklyIncrementable O>
  requires
    models::IndirectlyCopyable<I, O>
  tagged_pair<tag::in(I), tag::out(O)>
  copy(I first, S last, O result)
  {
    for (; first != last; ++first, ++result) {
      *result = *first;
    }
    return {__stl2::move(first), __stl2::move(result)};
  }


It uses concepts already, but, where is a terse syntax here?

auto copy(InputIterator first, Sentinel<InputIterator> last, OutputIterator result) requires IndirectlyCopyable<InputIterator, OutputIterator>

I'm not sure about whether `Sentinel<InputIterator>` can work as a constrained-type-name. But other than that though, what's the problem?
 
Is someone going to use it? Where is the most common and useful of cases applies?

Prove that the current syntax is not "the most common and useful of cases".
 
STL1 is not the only now, and most probably, it will not use terse syntax.

... again, so what? Does it need to?

Right now, the algorithms and iterator libraries can't effectively use concepts at all. This is due to the fact that it wasn't designed with such constraints in mind, and imposing reasonable constraints now will likely break a lot of people's code.

They could use some, such as the set of iterator requirements and such. But it should be noted that most of the "set of iterator requirements and such" can use terse syntax just fine.

Dealing with what `std::accumulate` should require of the value_type, for example, is not something we want to conceptualize.
 
Nobody has contested that full template syntax can do either one. Nobody has suggested that having terse syntax use one way means that the other way becomes impossible.

Suppose you writing a library with a lot of template functions. Some of them take arguments of the same, type. Some of them not. Suppose you want to constrain all you functions properly, so you use terse syntax for parameters with same type and usual template syntax for parameters that share same concept but not required to have same type.

Will you be writing it like that

template <R T2>

auto maybe_same(R p1, T2 p2)

auto same(R p1, R p2)

or for the sake of consistency, like that:

template <R T1, R T2>
auto maybe_same(T1 p1, T2 p2)

template <R T>
auto same(T p1, T p2)

?

I wouldn't expect someone to write the latter "for the sake of consistency". You write each function in the way that seems most natural for that function declaration. If one function can make use of terse syntax, I see no reason not to use it just because some other function can't.

Just like you don't use auto return type deduction on every function just for the sake of consistency. You use it where it makes the code clearer.

Without same-type rule it could be written like that:

auto maybe_same(R p1, R p2)

R {T} auto same(T p1, T p2)

Aaand consistent version looks kinda, better than others.. maybe just more familliar. But first version, i don't like it at all.

I can't say that I agree that this is "consistent". I guess it depends on what you mean by the term "terse templates".

The second function is still using a template declaration. It just doesn't use the word `template`. The spec is very clear that `concept_name{...}` is a form of template declaration called a "template-introduction declaration", which introduces template parameter names.

I consider "terse templates" to refer specifically to using concept names in place of typenames in function declarations. Especially since parameter constraints are the only way to apply constraints to lambdas (indeed, I'm kinda surprised that a lambda cannot use template-introduction declarations or even a `requires` clause at all).

So I can't say that I agree that these are more consistent. Then again, I don't agree that consistency on this point is a desirable feature...



Oh, one more thing. If you're going to copy pieces out of the text, don't leave the entire post you're replying to at the bottom. Just delete it before you post your response.

Ville Voutilainen

unread,
Nov 26, 2015, 1:07:40 PM11/26/15
to ISO C++ Standard - Future Proposals
On 26 November 2015 at 19:50, Nicol Bolas <jmck...@gmail.com> wrote:
> Or more to the point, if I do this:
>
> void foo(Thingy t)
> {
> Thingy j = <expr>;
> static_assert(is_same<decltype(t), decltype(j)>::value, "Why should this
> ever be false?");
> }
> Given absolutely no knowledge of what `Thingy` is, why should any user ever
> expect that assert to trigger? Or more to the point, if we define terse

With the current semantics as implemented by gcc, that assert may very
well trigger, so I don't follow
what point you're making here. Multiple occurrences of Thingy in the parameter
list are required to be of the same type, but occurrences of Thingy in
the function
body are not required to be of that type.

Tony V E

unread,
Nov 26, 2015, 2:33:34 PM11/26/15
to Andrew Tomazos
I completely agree with this analogy and think it is important and pertinent. 
+1

Sent from my BlackBerry portable Babbage Device
From: Andrew Tomazos
Sent: Wednesday, November 25, 2015 3:14 PM
Subject: Re: [std-proposals] Re: Question about P0121R0 (Concepts TS)

Tony V E

unread,
Nov 26, 2015, 2:40:58 PM11/26/15
to Andrei L
Yes. 

Once a developer has the 'essence' of Concepts,‎ ie that 'Container' means 'the type, whatever it is, must model/satisfy  Container', then 

f(Container a, Container b)

should/can/DOES have obvious meaning. ‎ That a and b model Container. No more, no less. If we want to say more, if we want to say 'same' we should need to state that. 

It isn't about terseness or ‎frequency of use. It is about what follows logically from the initial principles. Terseness shouldn't override that. 

Tony


Sent from my BlackBerry portable Babbage Device
From: Andrei L
Sent: Wednesday, November 25, 2015 7:09 PM
Subject: Re: [std-proposals] Question about P0121R0 (Concepts TS)

Vicente J. Botet Escriba

unread,
Nov 26, 2015, 5:12:18 PM11/26/15
to std-pr...@isocpp.org
Le 26/11/2015 20:40, Tony V E a écrit :
> Yes.
>
> Once a developer has the 'essence' of Concepts,‎ ie that 'Container' means 'the
> type, whatever it is, must model/satisfy Container', then
>
> f(Container a, Container b)
>
> should/can/DOES have obvious meaning. ‎ That a and b model Container. No more,
> no less. If we want to say more, if we want to say 'same' we should need to
> state that.
>
> It isn't about terseness or ‎frequency of use. It is about what follows
> logically from the initial principles. Terseness shouldn't override that.
+1

Vicente

Andrei L

unread,
Nov 26, 2015, 6:03:14 PM11/26/15
to std-pr...@isocpp.org
2015-11-26 22:50 GMT+05:00 Nicol Bolas <jmck...@gmail.com>:
And actually, you bring up a very important point. Namely, if we do things your way, we have to throw around a lot of `decltype(variableName)` to get the typename of a variable if you use terse syntax.

I agree, decltype will rise, and that is a drawback of a terse syntax. My way of doing things isn't a reason for that.


Why is your confusion more important than someone else's confusion?

It is not more important, it is just exist.

Apparently, i can ask the same question, it is just will be about my confusion being less important. But actually, not only mine, because i am not the first who asked, "Why same type?". So we back again on question about prioritizing users.

Somebody's going to be confused either way. It's either going to be the people who are acutely aware that using a constraint makes the function a template, or it's going to be the people who aren't aware of that.
 
As far as I'm concerned, the former group can handle it better than the latter.

Could you please elaborate on groups of users? What groups of users is related to first ones, and what to the second ones?


It requires knowing:

1) That you're using a constraint rather than a typename, despite all appearances to the contrary.

To use a constraint you required to know that you're using a constraint? ;)


2) That the rules for using a constraint in a parameter list are different from the rules of using a typename in a parameter list.

Yes? I showed that at the very beginning `R a` is `R<auto> a` type-name here is `auto`, not R.


Ducks, i brought you some amount of ducks. They look like a ducks, they quack like a ducks, and you treat them like a ducks. But then, i take duck consumes off, and you see that this is a cat, this is a dog, and this is a horse! And quacking sounds was made with little speakers! What you will do, put costumes back on, and say, "Nope, they're all ducks"?

You're arguing my case for me.
 
I, agree. This is more an example of the adapter pattern.
 
If the point of putting a duck costume on a cat/dog/horse is that it behave like a duck, then make it behave like a duck. While it's wearing the duck costume, it should be as indistinguishable as possible from a duck.

Now you're arguing for me. That is what i said, object's behavior matters, now what it actually is!
 
If I have to know whether it's a cat or dog or whatever, then why make it look like a duck at all?

Do you see now? As a writer of a function, you don't need to know. That is why you should not restrict user of your function to give it only objects of the same type.

If the point of terse template syntax is to make a constrained template function act like a non-template function, then it should make it act like a non-template function.

You said yourself that point of terse syntax is to minimize template boilerplate.
 
Constraints used in place of typenames should therefore act like typenames. That is essentially how the Concepts TS makes it work.

Do type-names make sure that every other parameter of a function have the same type?

Constraints can't fully act like type names, you saw that with your Thingy example, that is just can't happen, because they're not types. And you can't get rid of that.

Andrei L

unread,
Nov 26, 2015, 6:16:15 PM11/26/15
to std-pr...@isocpp.org

2015-11-26 22:50 GMT+05:00 Nicol Bolas <jmck...@gmail.com>:
I'm not sure about whether `Sentinel<InputIterator>` can work as a constrained-type-name. But other than that though, what's the problem?

The problem is that same-type-terse-syntax is not used. Most useful case did not applied.


Prove that the current syntax is not "the most common and useful of cases".

Ha! That is not working like that! Prove that it is.


STL1 is not the only now, and most probably, it will not use terse syntax.

... again, so what? Does it need to?

Right now, the algorithms and iterator libraries can't effectively use concepts at all. This is due to the fact that it wasn't designed with such constraints in mind, and imposing reasonable constraints now will likely break a lot of people's code.
 
You just proved that it is not.


I wouldn't expect someone to write the latter "for the sake of consistency"

Do you write curly braces on the same or on the next line? Do you jump between styles? That is, you choose one style and you write using that style.


Without same-type rule it could be written like that:
 
auto maybe_same(R p1, R p2)

R {T} auto same(T p1, T p2)

Aaand consistent version looks kinda, better than others.. maybe just more familliar. But first version, i don't like it at all.

I can't say that I agree that this is "consistent".

By consistent version i meant example with `template <typename>`s

Vadim Petrochenkov

unread,
Nov 26, 2015, 6:29:11 PM11/26/15
to ISO C++ Standard - Future Proposals
This summarizes my own genuine reaction on this problem well - "This is so logical and simple, why can't people see the truth?!" :)
But, heh, people have different backgrounds, different intuition...

Ville Voutilainen

unread,
Nov 26, 2015, 6:59:15 PM11/26/15
to ISO C++ Standard - Future Proposals
On 27 November 2015 at 01:16, Andrei L <aend...@gmail.com> wrote:
>> Prove that the current syntax is not "the most common and useful of
>> cases".
> Ha! That is not working like that! Prove that it is.


Perhaps it doesn't work like that for a discussion. But that is the way it works
if you want to change a specification or a working draft; in order to
make a change,
you need to convince enough people to attain consensus to make a
change, otherwise
status quo will prevail.

Andrei L

unread,
Nov 26, 2015, 7:10:47 PM11/26/15
to std-pr...@isocpp.org

2015-11-27 4:59 GMT+05:00 Ville Voutilainen <ville.vo...@gmail.com>:
Perhaps it doesn't work like that for a discussion. But that is the way it works
if you want to change a specification or a working draft; in order to
make a change,
you need to convince enough people to attain consensus to make a
change, otherwise
status quo will prevail.

No one doubts that.

That exchange was in context of the discussion or I am misunderstood something?

Casey Carter

unread,
Nov 26, 2015, 11:59:12 PM11/26/15
to ISO C++ Standard - Future Proposals
On Thursday, November 26, 2015 at 11:50:46 AM UTC-6, Nicol Bolas wrote:

What about Eric Niebler's and Casey Carter's STL2 version?

template <InputIterator I, Sentinel<I> S, WeaklyIncrementable O>
  requires
    models::IndirectlyCopyable<I, O>
  tagged_pair<tag::in(I), tag::out(O)>
  copy(I first, S last, O result)
  {
    for (; first != last; ++first, ++result) {
      *result = *first;
    }
    return {__stl2::move(first), __stl2::move(result)};
  }


It uses concepts already, but, where is a terse syntax here?

auto copy(InputIterator first, Sentinel<InputIterator> last, OutputIterator result) requires IndirectlyCopyable<InputIterator, OutputIterator>

I'm not sure about whether `Sentinel<InputIterator>` can work as a constrained-type-name. But other than that though, what's the problem?
 

According to N4553, a partial-concept-id is perfectly acceptable as a placeholder in a parameter declaration of an abbreviated function template - there's even an example that includes such a usage. However, there's nothing in N4553 to indicate that the replacement of placeholder parameter types with invented template parameters in the parameter-declaration-clause also happens in an associated requires-clause. That does seem like a consistent extension to me, and it would allow terse syntax to be applicable to many more cases. Someone should propose it.

(You also technically need to replace the occurrences of OutputIterator with WeaklyIncrementable for this declaration to be equivalent, but I'm nitpicking and it has no bearing on the discussion.)

Nicol Bolas

unread,
Nov 27, 2015, 10:55:16 AM11/27/15
to ISO C++ Standard - Future Proposals

... I see what I did now.

I misread 7.1.6.4, p4. It's talking about the equivalence between constrained-type-specifiers in parameter lists and the return type, but I thought it operated throughout the entire function definition.

So never mind.

Nicol Bolas

unread,
Nov 27, 2015, 11:33:25 AM11/27/15
to ISO C++ Standard - Future Proposals


On Thursday, November 26, 2015 at 6:16:15 PM UTC-5, Andrei L wrote:

2015-11-26 22:50 GMT+05:00 Nicol Bolas <jmck...@gmail.com>:
I'm not sure about whether `Sentinel<InputIterator>` can work as a constrained-type-name. But other than that though, what's the problem?

The problem is that same-type-terse-syntax is not used. Most useful case did not applied.

My point was that the current Concepts TS does not in any way preclude you from writing this using abbreviated function template syntax. It doesn't matter if this particular function doesn't make use of type consistency in abbreviated functions. So long as it doesn't preclude you from writing this example, it's meaningless.

Prove that the current syntax is not "the most common and useful of cases".

Ha! That is not working like that! Prove that it is.

Who has the burden of proof here? Concepts TS is an ISO standard that is being implemented as we speak. Therefore, the person requesting a change is the one upon whom the burden of proof rests. Not to mention, providing evidence always makes your case stronger, even if the burden of proof isn't on you.

And it's not even that hard. If you want to prove which is more common, just go through the standard library and ranges TS algorithms, and see how many of them could use abbreviated function template syntax under the current concepts TS and how many could use it under your proposed changes.

If you are correct about what is "the most common and useful of cases", then evidence shouldn't be that hard to find, right?

STL1 is not the only now, and most probably, it will not use terse syntax.

... again, so what? Does it need to?

Right now, the algorithms and iterator libraries can't effectively use concepts at all. This is due to the fact that it wasn't designed with such constraints in mind, and imposing reasonable constraints now will likely break a lot of people's code.
 
You just proved that it is not.

*ahem*: "imposing reasonable constraints now will likely break a lot of people's code."

Nothing I showed proved in any way that that statement was false. Yes, you could conceptualize the standard library. But you can't do it retroactively, when people have been (silently) violating constraints for decades.
 
I wouldn't expect someone to write the latter "for the sake of consistency"

Do you write curly braces on the same or on the next line? Do you jump between styles? That is, you choose one style and you write using that style.

Are we even talking about the same thing? Because your statement here doesn't seem to address the point.

You will not be able to write everything in terse syntax, and there's no getting around that fact. Because of that, you will have to select your syntax based on what you can use for your particular use case. You will have functions that can use terse syntax and you will have functions that can't.

Consistency of using terse syntax is not achievable. Therefore, what does it matter if some cases require falling back to full template syntax?

Without same-type rule it could be written like that:
 
auto maybe_same(R p1, R p2)

R {T} auto same(T p1, T p2)

Aaand consistent version looks kinda, better than others.. maybe just more familliar. But first version, i don't like it at all.

I can't say that I agree that this is "consistent".

By consistent version i meant example with `template <typename>`s

It's obvious you meant that, but that doesn't make it consistent as far as I'm concerned. You're still introducing a template parameter, just with a different syntax that doesn't use the word "template". In fact, this form of `same` doesn't even use abbreviated function template syntax at all. So it doesn't trigger that part of the compiler.

Nicol Bolas

unread,
Nov 27, 2015, 11:38:39 AM11/27/15
to ISO C++ Standard - Future Proposals
On Thursday, November 26, 2015 at 11:59:12 PM UTC-5, Casey Carter wrote:
On Thursday, November 26, 2015 at 11:50:46 AM UTC-6, Nicol Bolas wrote:

What about Eric Niebler's and Casey Carter's STL2 version?

template <InputIterator I, Sentinel<I> S, WeaklyIncrementable O>
  requires
    models::IndirectlyCopyable<I, O>
  tagged_pair<tag::in(I), tag::out(O)>
  copy(I first, S last, O result)
  {
    for (; first != last; ++first, ++result) {
      *result = *first;
    }
    return {__stl2::move(first), __stl2::move(result)};
  }


It uses concepts already, but, where is a terse syntax here?

auto copy(InputIterator first, Sentinel<InputIterator> last, OutputIterator result) requires IndirectlyCopyable<InputIterator, OutputIterator>

I'm not sure about whether `Sentinel<InputIterator>` can work as a constrained-type-name. But other than that though, what's the problem?
 

According to N4553, a partial-concept-id is perfectly acceptable as a placeholder in a parameter declaration of an abbreviated function template - there's even an example that includes such a usage. However, there's nothing in N4553 to indicate that the replacement of placeholder parameter types with invented template parameters in the parameter-declaration-clause also happens in an associated requires-clause. That does seem like a consistent extension to me, and it would allow terse syntax to be applicable to many more cases. Someone should propose it.

That sounds more like a defect. Granted, you can `decltype` your way around it, so they may just NAD it. But it does seem quite odd.

Andrei L

unread,
Nov 27, 2015, 8:47:12 PM11/27/15
to std-pr...@isocpp.org

2015-11-27 21:33 GMT+05:00 Nicol Bolas <jmck...@gmail.com>:
It's obvious you meant that, but that doesn't make it consistent as far as I'm concerned.
 
I wouldn't expect someone to write the latter "for the sake of consistency"
 
Do you write curly braces on the same or on the next line? Do you jump between styles? That is, you choose one style and you write using that style.

Are we even talking about the same thing? Because your statement here doesn't seem to address the point.

Consistency of using same syntax, and not jumping around using different style of code? Where do you write braces, naming convention, other things like that. This is important. What syntax to use for declaring template functions, and when, will become part of these things.


You're still introducing a template parameter, just with a different syntax that doesn't use the word "template". In fact, this form of `same` doesn't even use abbreviated function template syntax at all. So it doesn't trigger that part of the compiler.

I, wasn't wha..

Are we even talking about the same thing?

It seems like we're not. Misunderstanding happened at some point.

You will not be able to write everything in terse syntax, and there's no getting around that fact. Because of that, you will have to select your syntax based on what you can use for your particular use case. You will have functions that can use terse syntax and you will have functions that can't.

Consistency of using terse syntax is not achievable. Therefore, what does it matter if some cases require falling back to full template syntax?

Yes, yes i agree with that. With what i don't agree is that using same concept for different parameters forces me to use different syntax to explicitly specify that i don't care if they have same type or not.

My point was that the current Concepts TS does not in any way preclude you from writing this using abbreviated function template syntax. It doesn't matter if this particular function doesn't make use of type consistency in abbreviated functions. So long as it doesn't preclude you from writing this example, it's meaningless.

I asked wrong question.


template <InputIterator I, Sentinel<I> S, WeaklyIncrementable O>
  requires
    models::IndirectlyCopyable<I, O>
  tagged_pair<tag::in(I), tag::out(O)>
  copy(I first, S last, O result)

That question, "It uses concepts already, but, where is a terse syntax here?", should  be, "<..> where is our common case here?"

And it's not even that hard. If you want to prove which is more common, just go through the standard library and ranges TS algorithms, and see how many of them could use abbreviated function template syntax under the current concepts TS and how many could use it under your proposed changes.

Ranges TS - none, because it uses iterator sentinel pairs. STL2 - none, for same reason. STL - could use, could not, you can't say, and as we found, abbreviated function template syntax forces us to use decltype, so i'd expect that `template <typename T>` will stay.


If you are correct about what is "the most common and useful of cases", then evidence shouldn't be that hard to find, right?

Ranges TS and STL2. That is enough to drop "the most common" part. That is also enough to drop "the most useful" part.


Who has the burden of proof here? Concepts TS is an ISO standard that is being implemented as we speak. Therefore, the person requesting a change is the one upon whom the burden of proof rests. Not to mention, providing evidence always makes your case stronger, even if the burden of proof isn't on you.

I showed example, where same-type rule is not helping and is not used, and has no use. I showed example where it is questionable if it will be used or not, so you can't rely on that. Then i asked, "Where is it applies?" What i got? "Prove that it's not". Like, if you can't prove that it's not then it is true, but where is a proof that it is true?

I came here because i was curious, why that decision was made. I wanted to know a reason, but all what was behind that is - because STL uses iterator pairs (Thing that share same concept are having same type, because in STL begin and end iterators have same type. Great reason).


auto copy(InputIterator first, Sentinel<InputIterator> last, OutputIterator result) requires IndirectlyCopyable<InputIterator, OutputIterator>

Now, finally, with this example it is obvious, why would you want same concept names be replaced with same type. Not excuses with confused library writers, and users familiar with templates (you don't expect just starters writing a template functions and classes right?).

So, from one hand, there is cases where same-type rule can be useful, there is also cases where it is not, from the other hand this rule is illogical, and without that rule you could have a way to care only about objects behavior and not it's actual type, and all what you loose is that at some cases you need to explicitly specify that some things have same type, when you want them to have same type.

At that point i have nothing more to say.

Vicente J. Botet Escriba

unread,
Nov 28, 2015, 8:12:05 AM11/28/15
to std-pr...@isocpp.org
Could you clarify why this would be a defect?

Vicente

barry....@gmail.com

unread,
Dec 3, 2015, 3:46:46 PM12/3/15
to ISO C++ Standard - Future Proposals
I'll throw in a contrary opinion here. What is the advantage of having abbreviated function templates at all? It's barely even terser. The following:

void foo(InputIterator a, InputIterator b);

is a mere SEVEN characters shorter than the MUCH more understandable:

template <InputIterator It>
void foo(It a, It b);

Not to mention that the former devolves into this large argument about whether or not a or b can have the same type or not, and whether or not that makes sense or is reasonable, and cannot even support two different types that both meet the InputIterator criteria. In the latter, it is obvious that a and b have the same type and it is obvious how to provide for two different InputIterators. 

Seriously. Why.  

On Monday, November 23, 2015 at 10:27:37 PM UTC-6, Andrei L wrote:
Hello,

I've been reading through the paper and noticed this:

<..> An abbreviated function template is equivalent to a function template (14.6.6) whose template-parameter-list includes one invented template-parameter for each occurrence of a placeholder in the parameter-declaration-clause, in order of appearance <..>

Is this means that in such code:

template <typename T>
concept bool R = CheckSomething<T>;

auto foo(R a, R b) { /* ... */ }

parameters `a' and `b' have the same type? And if so, why? What is the rationale behind that?

Jonathan Coe

unread,
Dec 3, 2015, 4:30:10 PM12/3/15
to std-pr...@isocpp.org
There's a strong push to rid modern c++ of its perceived verbosity. Range-based-for, auto and the proposed auto {x,y,z} for tuple result capture are all illustrative of this.

I'm sure there are deeper reasons for such a drive beyond reducing character count.

I'm an engineer not a language designer though. How will reducing the verbosity alter the way we use the language?
--

Ren Industries

unread,
Dec 3, 2015, 5:42:11 PM12/3/15
to std-pr...@isocpp.org
Have you heard of the Sapir–Whorf hypothesis? 

In essence, the idea is the tools we use for thought change how we think. While disproven in its strong form, it's clearly obvious in its weak form in C++11 through an example: lambdas.
Lambdas don't really add any "power"; you can easily simulate them via an explicit named functor, with explicit capture, which I still do in C++03.
However, they do make it much easier; for_each with lambdas is clean and easy, where as with explicit functors it is rarely worthwhile.

Another example is constexpr; it is entirely possible to do without constexpr, but would you want to write the metatemplate code every time?
In reality, since it is so verbose, you'd probably only write it when you absolutely need the speed (such as numerical libraries).
With constexpr, it becomes trivial, which means you'll almost certainly do it more, leading to faster, more elegant programs.

By making features less verbose, we make it easier to get simple things done; while this adds no computational power to the language, I'd argue it adds "linguistic power".
By adding this linguistic power, we change how people code.
By changing how people code, we can increase productivity and/or speed of execution.
Reply all
Reply to author
Forward
0 new messages