calling templated operator() for functors as regular functions (gcc implementation available)

681 views
Skip to first unread message

bastie...@gmail.com

unread,
Sep 18, 2017, 12:47:43 PM9/18/17
to ISO C++ Standard - Future Proposals
Hi,

Now that the  Familiar template syntax for generic lambdas proposal has been accepted in C++20,
I think it would be interesting to be able to call such lambdas (and functors) with explicit template parameters using the same syntax as for templated functions.
For now we must call explicitly .operator() to give said parameters which defeats the purpose of operator().
auto f = []<class T>() { ... };
f
<int>(); //error expected primary expression before 'int'
f
.operator()<int>(); //the only way

What I propose is to update the postfix-expression part of the standard (8.2) to allow :
postfix-expression < template-parameter-listopt > ( expression-listopt )

iff the type of postfix-expression has a templated operator() and has no operator< .

I've made a patch of 100 lines for gcc-7.2 that implements this idea and you can try a live version of the compiler here : compiler explorer.

Here's an exemple:
struct A { template<class T> void operator()(T&&) {} };
struct B : A { template<class T> bool operator<(T&&) { return true; } };

int main()
{
 
constexpr auto size_of = []<class T>() { return sizeof(T); };
 
static_assert(size_of<int>() == sizeof(int));

 A no_lt_op
;
 B with_lt_op
;

 no_lt_op
<int>(42); //ok
 no_lt_op
<42>(42); //try to call method, doesn't match, error
 with_lt_op
<int>(42); //sees operator<, doesn't try to interpret as '.operator()'
 
[](){}<int>(); //no templated operator() so postfix-expression is '[](){}', try to parse a relational-expression, error: expected primary-expression before 'int'
}

Matt Calabrese

unread,
Sep 18, 2017, 1:06:39 PM9/18/17
to ISO C++ Standard - Future Proposals
On Mon, Sep 18, 2017 at 12:47 PM, <bastie...@gmail.com> wrote:
What I propose is to update the postfix-expression part of the standard (8.2) to allow :
postfix-expression < template-parameter-listopt > ( expression-listopt )

iff the type of postfix-expression has a templated operator() and has no operator< .

What do you mean by "has no operator<"? The operator< that is found when doing comparisons is frequently a non-member (comparisons are generally recommended to be implemented as non-member overloads, not member overloads).

I think that what we have already (or rather what we are slated to have in C++20) is fine.

-- Matt Calabrese

Nicol Bolas

unread,
Sep 18, 2017, 1:07:47 PM9/18/17
to ISO C++ Standard - Future Proposals, bastie...@gmail.com
I don't see a great need for this.

Look at the motivation section for "Familiar template syntax for generic lambdas." All of the motivating examples still rely on function template argument deduction to figure out what those template parameters are. There is no expectation that people will be writing lambdas where you explicitly provide some template parameters.

So, why do we need this ability? How often do we need to create functors where you specify template parameters directly? Is that something we really want to encourage?

Nicol Bolas

unread,
Sep 18, 2017, 1:13:47 PM9/18/17
to ISO C++ Standard - Future Proposals


On Monday, September 18, 2017 at 1:06:39 PM UTC-4, Matt Calabrese wrote:
On Mon, Sep 18, 2017 at 12:47 PM, <bastie...@gmail.com> wrote:
What I propose is to update the postfix-expression part of the standard (8.2) to allow :
postfix-expression < template-parameter-listopt > ( expression-listopt )

iff the type of postfix-expression has a templated operator() and has no operator< .

What do you mean by "has no operator<"? The operator< that is found when doing comparisons is frequently a non-member (comparisons are generally recommended to be implemented as non-member overloads, not member overloads).

Also, the `operator<` issue becomes more important when dealing with non-type template parameters. Non-member overloads for `operator<` are more prevalent for heterogeneous comparisons, which non-type template parameters frequently will be.

Zhihao Yuan

unread,
Sep 18, 2017, 1:14:44 PM9/18/17
to std-pr...@isocpp.org, bastie...@gmail.com
On Mon, Sep 18, 2017 at 12:07 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> I don't see a great need for this.
>
> [...]
>
> So, why do we need this ability? How often do we need to create functors
> where you specify template parameters directly? Is that something we really
> want to encourage?
>

I feel this is useful, otherwise you can't
write functions like `make_unique<T>`
with lambdas -- where the type/non-type
parameters are there only for manually
input.

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

Nicol Bolas

unread,
Sep 18, 2017, 1:21:42 PM9/18/17
to ISO C++ Standard - Future Proposals, bastie...@gmail.com, z...@miator.net
On Monday, September 18, 2017 at 1:14:44 PM UTC-4, Zhihao Yuan wrote:
On Mon, Sep 18, 2017 at 12:07 PM, Nicol Bolas <jmck...@gmail.com> wrote:
> I don't see a great need for this.
>
> [...]
>
> So, why do we need this ability? How often do we need to create functors
> where you specify template parameters directly? Is that something we really
> want to encourage?
>

I feel this is useful, otherwise you can't
write functions like `make_unique<T>`
with lambdas -- where the type/non-type
parameters are there only for manually
input.

Do we need to be able to write lambdas like that? That's my question: what is the practical use case for this?

You can't pass template functions around. So there's not a lot of code that takes a function by parameter and wants to instantiate a template on the `operator()` call. So why is it important that we provide this functionality?

Now, if we had lifting lambdas, that might be a reason to do so (though it still represents an unusual scenario). But other than that, I just don't see the benefit beyond the theoretical.

We don't need lambda functions to be able to do everything that non-lambda functions can do.

Zhihao Yuan

unread,
Sep 18, 2017, 1:43:39 PM9/18/17
to Nicol Bolas, ISO C++ Standard - Future Proposals, bastie...@gmail.com, Zhihao Yuan
On Mon, Sep 18, 2017 at 12:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> Do we need to be able to write lambdas like that? That's my question: what
> is the practical use case for this?

Yes, because lambda can capture, (template)
functions cannot.

>
> We don't need lambda functions to be able to do everything that non-lambda
> functions can do.

But we need lambda to do more things non-lambda
cannot do :) Because it can capture, so IMHO it
deserve higher priority to evolve.

bastie...@gmail.com

unread,
Sep 18, 2017, 4:10:02 PM9/18/17
to ISO C++ Standard - Future Proposals
Le lundi 18 septembre 2017 19:06:39 UTC+2, Matt Calabrese a écrit :
On Mon, Sep 18, 2017 at 12:47 PM, <bastie...@gmail.com> wrote:
What I propose is to update the postfix-expression part of the standard (8.2) to allow :
postfix-expression < template-parameter-listopt > ( expression-listopt )

iff the type of postfix-expression has a templated operator() and has no operator< .

What do you mean by "has no operator<"? The operator< that is found when doing comparisons is frequently a non-member (comparisons are generally recommended to be implemented as non-member overloads, not member overloads).
 
I hadn't thought about that. You're right and because of that we would have things like this:
//a.hpp
template<class T, class V> constexpr bool operator<(A, B) { return true; };
//b.cpp
constexpr auto l = []<class T>() {...};
constexpr auto r_0 = l<int>(); //ok;
#include "a.hpp"
constexpr auto r_1 = l<int>(); //no longer possible

Two things I failed to mention in my previous comment though
- checking for operators excludes scalar types.
- if an attempt to call operator() failed in postfix-expression it fallback on the normal behavior (live exemple).
template<class T, class V> bool operator<(T, V) { return true;}
bool r = ([](){})<5>(10);//calls operator< with current implementation
([]<int>(int))<5>(10); //calls operator()<5>(10)
The idea of checking for operator< was just to have a simple test to quickly fallback on the default behavior.
Would it be better to change relational-operators then, and fallback on operator() if it doesn't work and has a templated operator().

That being said on the motivation side,
the reason why I believe this desired is that lambda have both capture and a limited scope.
Which give the ability to get rid of all these detail::xxx_impl functions that we see so often in the standard library.

An other example of use case would be something like this for instance.
template<size_t from, size_t to>
constexpr auto apply_sequence_from_to()
{
 
static_assert(to >= i);
 
return []<size_t... I>(std::index_sequence<I...>)
 
{
   
return []<template<class...> Holder>(auto&& getter)
   
{
     
return Holder {getter<(I+from)>()...};
   
};
 
}(std::make_index_sequence<to - from>{});
}

std
::tuple a = {...};
decltype(a) b = {...};
auto sequence = apply_sequence_from_to<2,5>();
auto added = sequence<std::tuple>([&]<size_t I>() { return std::get<I>(a) + std::get<I>(b); });
auto numbers = sequence<std::tuple>([&]<size_t I>() { return I; });


Nicol Bolas

unread,
Sep 18, 2017, 5:16:58 PM9/18/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net
On Monday, September 18, 2017 at 1:43:39 PM UTC-4, Zhihao Yuan wrote:
On Mon, Sep 18, 2017 at 12:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> Do we need to be able to write lambdas like that? That's my question: what
> is the practical use case for this?

Yes, because lambda can capture, (template)
functions cannot.

I said "practical use case". I specifically did not want to talk about what you could do from a theoretical standpoint.

Where would I have need of such a thing? Give me a real-world example of code where you have a genuine need for the combination:

1: The sender needs to pass a function to some receiver.

2: The receiver has template parameters that they need to specify, which cannot be deduced from arguments.

3: The sender needs to have the function capture values form the local scope where the function is provided.

4: The receiver should not call a named member of the functor. That is, it is more natural for the receiver to use `operator()` than a member with a real name.

I have yet to encounter any situation where the 4 of those traits all happened at once. In particular is #4.

>
> We don't need lambda functions to be able to do everything that non-lambda
> functions can do.

But we need lambda to do more things non-lambda
cannot do :) Because it can capture, so IMHO it
deserve higher priority to evolve.

You could use that logic to justify any change to lambdas. Wanting to see lambdas "evolve" is not justification for this "evolution".

Zhihao Yuan

unread,
Sep 18, 2017, 5:50:20 PM9/18/17
to Nicol Bolas, ISO C++ Standard - Future Proposals, bastie...@gmail.com, Zhihao Yuan
On Mon, Sep 18, 2017 at 4:16 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> I said "practical use case". I specifically did not want to talk about what
> you could do from a theoretical standpoint.
>
> Where would I have need of such a thing? Give me a real-world example of
> code where you have a genuine need for the combination:
>
> 1: The sender needs to pass a function to some receiver.
>
> 2: The receiver has template parameters that they need to specify, which
> cannot be deduced from arguments.
>
> 3: The sender needs to have the function capture values form the local scope
> where the function is provided.
>
> 4: The receiver should not call a named member of the functor. That is, it
> is more natural for the receiver to use `operator()` than a member with a
> real name.
>
> I have yet to encounter any situation where the 4 of those traits all
> happened at once. In particular is #4.
>

a) Partial apply a flag/dispatching tag:

// ...
auto writer = [&fp]<buffer_policy x> (char* p, size_t sz)
{
writeall(fp, x | other_flags, p, sz);
// ...
}

rsync_writer rs(writer<buffer_policy::unbuffered>);
// ...
stream_formatter fmt(writer<buffer_policy::line_bufferred>);
// ...
}

b) Extra type checks:

// ...
auto get_value = [&parser]<template T>()
{
if constexpr (std::is_integral<T>)
{
long long i;
return parser.readll(&i);
// bound check
return T(i);
} else if constexpr (...)
// ...
}

auto hdr = get_value<int>();
// process...
std::generate_n(back_inserter(v), n, get_value<float>);

Nicol Bolas

unread,
Sep 18, 2017, 7:06:16 PM9/18/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net
First, you may have confused the proposed feature here. The feature is not "`functor<args>` transforms itself into a functor that gets called with those arguments." The feature is "`functor<args>(...)` is equivalent to `functor.operator()<args>(...)`".

So `writer<buffer_policy::unbuffered>` is not a function object. It's not what this proposal is.

And even if it was, that's only making things worse. You're making a lambda that creates lambdas.

Second, example b doesn't qualify, since you gain absolutely nothing by making that a lambda instead of making it a member function of the parser type or just a named struct. This is especially true considering how big that lambda is likely to be; defining it inline makes your code less readable not more readable.

Indeed, example b is precisely why we shouldn't do this. It encourages coding styles that use lambdas gratuitously, rather than taking 10 extra seconds to write a real type.

bastie...@gmail.com

unread,
Sep 18, 2017, 8:27:26 PM9/18/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net
First thing, C++ devs never think about the pain to use their API.
//let me remind you that this is the standard way to use std::apply with an templated function
template<class... Args> void printer(Args&&...) {...}
std
::apply([]<class T>(T&&... args) { printer(std::forward<Args>(args)...); }, some_tuple);
Which means that devs will return non-deducible templated lambdas if it allows them do something they want to do and can't otherwise (and it does).

Le lundi 18 septembre 2017 23:16:58 UTC+2, Nicol Bolas a écrit :
3: The sender needs to have the function capture values form the local scope where the function is provided.

no,  it can if it wants to.
sequence<std::tuple>([]<size_t I>() { return I; });
constexpr auto tuple = {...};
sequence
<std::tuple>([]<size_t I>() {return std::get<I>(tuple);};//no capture required for constexpr variables
4: The receiver should not call a named member of the functor. That is, it is more natural for the receiver to use `operator()` than a member with a real name.
 
A) In no way are we preventing operator() to be called explicitly.
B) 
sequence.operator()<std::tuple>([]<size_t I>() { return I; }); // is more natural for you ?
sequence
.template operator()<std::tuple>([]<size_t I>() { return I; }); //how about this?

they don't seem to agree. 


| Second, example b doesn't qualify, since you gain absolutely nothing by making that a lambda instead of making it a member function of the parser type or just a named struct. This is especially true considering how big that lambda is likely to be; defining it inline makes your code less readable not more readable.
Yes it does.
You ass-su-me that parser is a type created by the caller,
You ass-su-me that get_value will be used outside of the scope local,
You believe that this is more readable?
You believe that this is more efficient?
struct struct_used_once_for_no_reason
{
     borring_qualified_long_name
&parser;

     struct_used_once_for_no_reason
(borring_qualified_long_name &parser) : parser(parser) {}

     
template <template T>
     
constexpr auto  meaningless_name()
     
{

       
if constexpr (std::is_integral<T>)         {
           
long long i;
           
return parser.readll(&i);
           
// bound check
           
return T(i);
       
} else if constexpr (...)
           
// ...
     
}
};

//N lines later

template<...>
auto somefunc(...)
{
   
...
   struct_used_once_for_no_reason get_value(parser);
      auto hdr = get_value.meaningless_name<int>();
     
//process
      std::generate_n(back_inserter(v), n, get_value.meaningless_name<float>());
      //other stuff, will never use get_value again
}

So you disseminated your code around and done by hand what the compiler knows best to do, well done.

F.50: Use a lambda when a function won’t do (to capture local variables, or to write a local function)

Reason
 

Functions can’t capture local variables or be declared at local scope; if you need those things, prefer a lambda where possible...



ES.28: Use lambdas for complex initialization, especially of constvariables
Reason

 

It nicely encapsulates local initialization, including cleaning up scratch variables needed only for the initialization, without needing to create a needless nonlocal yet nonreusable function. It also works for variables that should be const but only after some initialization work...

Zhihao Yuan

unread,
Sep 18, 2017, 9:02:04 PM9/18/17
to Nicol Bolas, ISO C++ Standard - Future Proposals, bastie...@gmail.com, Zhihao Yuan
On Mon, Sep 18, 2017 at 6:06 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>> std::generate_n(back_inserter(v), n, get_value<float>);
>
> First, you may have confused the proposed feature here. The feature is not
> "`functor<args>` transforms itself into a functor that gets called with
> those arguments." The feature is "`functor<args>(...)` is equivalent to
> `functor.operator()<args>(...)`".
>

Start from considering whether my piece of code is
desirable to write, right? The b) example clearly
shows that I want the data member to be shared
across the lambda instantiations, thus I'm not asking
for creating new lambda objects.

Then a follow-up question is that why `functor<args>`
can't mean `functor.operator<args>`, and then we
realize that we miss lambda's "this" pointer here, but
since lambda's member functions' addresses are
compile time constants, thus they can be a part of
the type, so we only need to pass the `this` pointers
-- and that's the basic implementation strategy.
Going further, ref captures &p are not required to
be implemented as reference members, thus we
could just passing &p if that's the only capture, one
less indirections.

> [..] defining it inline makes your code less
> readable not more readable.
>

I just want to "express ideas directly in code"
(p0559r0 A.1, also from D&E). I mean function,
then give me a function. Lambda is mathematically
a function, deal.

Richard Smith

unread,
Sep 18, 2017, 9:16:00 PM9/18/17
to std-pr...@isocpp.org
On 18 September 2017 at 09:47, <bastie...@gmail.com> wrote:
Hi,

Now that the  Familiar template syntax for generic lambdas proposal has been accepted in C++20,
I think it would be interesting to be able to call such lambdas (and functors) with explicit template parameters using the same syntax as for templated functions.
For now we must call explicitly .operator() to give said parameters which defeats the purpose of operator().
auto f = []<class T>() { ... };
f
<int>(); //error expected primary expression before 'int'
f
.operator()<int>(); //the only way

What I propose is to update the postfix-expression part of the standard (8.2) to allow :
postfix-expression < template-parameter-listopt > ( expression-listopt )

iff the type of postfix-expression has a templated operator() and has no operator< .

This kind of disambiguation is more horrible than perhaps you realise. You can't do this lookup if the postfix-expression has a dependent type. How would you disambiguate in that case? Eg:

template<typename T> void f(T t) {
  t<0>(1); // template or non-template?
  template t<0>(1); // would you allow this?
}
void g() {
  f(1);
  f([]<int>(){});
}

The problem with an overloaded operator< has been raised by others, but note that the left-hand type of the < might not even be the type of the postfix-expression:

// I want to be able to write a(b + c) as shorthand for a * (b + c).
struct MyInt {
  int value;
  MyInt(int v) : value(v) {}
  template<typename T> decltype(auto) operator()(T t) { return value * t; }
};
MyInt operator*(MyInt, MyInt);
MyInt operator+(MyInt, MyInt);
bool operator<(MyInt, MyInt);

bool do_stuff(MyInt x) {
  return 7 + x < x(x + 1); // oops, your proposal parses this as a template argument list
}

Your implementation rejects this currently-valid code.
 
I've made a patch of 100 lines for gcc-7.2 that implements this idea and you can try a live version of the compiler here : compiler explorer.

Here's an exemple:
struct A { template<class T> void operator()(T&&) {} };
struct B : A { template<class T> bool operator<(T&&) { return true; } };

int main()
{
 
constexpr auto size_of = []<class T>() { return sizeof(T); };
 
static_assert(size_of<int>() == sizeof(int));

 A no_lt_op
;
 B with_lt_op
;

 no_lt_op
<int>(42); //ok
 no_lt_op
<42>(42); //try to call method, doesn't match, error
 with_lt_op
<int>(42); //sees operator<, doesn't try to interpret as '.operator()'
 
[](){}<int>(); //no templated operator() so postfix-expression is '[](){}', try to parse a relational-expression, error: expected primary-expression before 'int'
}

--
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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/8a389d85-515a-4ca8-8451-6e366f8e655e%40isocpp.org.

Nicol Bolas

unread,
Sep 18, 2017, 9:36:53 PM9/18/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net
On Monday, September 18, 2017 at 8:27:26 PM UTC-4, bastie...@gmail.com wrote:
First thing, C++ devs never think about the pain to use their API.
//let me remind you that this is the standard way to use std::apply with an templated function
template<class... Args> void printer(Args&&...) {...}
std
::apply([]<class T>(T&&... args) { printer(std::forward<Args>(args)...); }, some_tuple);
Which means that devs will return non-deducible templated lambdas if it allows them do something they want to do and can't otherwise (and it does).

... so? That's a completely different problem, one that we've had several proposals towards solving.

None of them involve what you're talking about.

Le lundi 18 septembre 2017 23:16:58 UTC+2, Nicol Bolas a écrit :
3: The sender needs to have the function capture values form the local scope where the function is provided.

no,  it can if it wants to.

Why are you using a lambda if you don't need to capture values?
 
sequence<std::tuple>([]<size_t I>() { return I; });
constexpr auto tuple = {...};
sequence
<std::tuple>([]<size_t I>() {return std::get<I>(tuple);};//no capture required for constexpr variables
4: The receiver should not call a named member of the functor. That is, it is more natural for the receiver to use `operator()` than a member with a real name.
 
A) In no way are we preventing operator() to be called explicitly.

... I think you misunderstood my point.

The choices are not between using your syntax and using `.operator()<...>`. The choices are between using your syntax and using a named member function. That is, one that has a meaningful name, not `operator()`.

See, the problem comes from your insistence on naming this member function `operator()`, because you insist on writing it as a lambda. Once you stop writing it as a lambda, you can give the function a real name with real template arguments. And therefore, the problem disappears entirely.

B) 
sequence.operator()<std::tuple>([]<size_t I>() { return I; }); // is more natural for you ?
sequence
.template operator()<std::tuple>([]<size_t I>() { return I; }); //how about this?


How about neither? I don't understand what that code is trying to accomplish, so it's kind of hard to say what's the right answer.
I'm not sure what you're trying to say with those links.

| Second, example b doesn't qualify, since you gain absolutely nothing by making that a lambda instead of making it a member function of the parser type or just a named struct. This is especially true considering how big that lambda is likely to be; defining it inline makes your code less readable not more readable.
Yes it does.
You ass-su-me that parser is a type created by the caller,
You ass-su-me that get_value will be used outside of the scope local,
You believe that this is more readable?
You believe that this is more efficient?

It would be more readable if you didn't make such obnoxious names for the types.

Functions should be short; that makes it easy to read them and reason about them. All of that `if constexpr` logic takes up a lot of room. Room that's taking up space in your function that's, again, supposed to be short. By extracting all of that complex `if constexpr` code out into its own separate place, you make the function that uses it much more digestible.

Lastly, I fail to see how that either way would be "more efficient" than the other.

Nicol Bolas

unread,
Sep 18, 2017, 9:51:08 PM9/18/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net
On Monday, September 18, 2017 at 9:02:04 PM UTC-4, Zhihao Yuan wrote:
On Mon, Sep 18, 2017 at 6:06 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>>   std::generate_n(back_inserter(v), n, get_value<float>);
>
> First, you may have confused the proposed feature here. The feature is not
> "`functor<args>` transforms itself into a functor that gets called with
> those arguments." The feature is "`functor<args>(...)` is equivalent to
> `functor.operator()<args>(...)`".
>

Start from considering whether my piece of code is
desirable to write, right?

First, no, we don't have to start there. This thread is not about making that code writable, no matter how "desirable" you may find it.

Second, no it isn't desirable. I've certainly never needed to write such a thing. I'd much rather have lifting lambdas or abbreviated lambdas or somesuch.

The b) example clearly
shows that I want the data member to be shared
across the lambda instantiations, thus I'm not asking
for creating new lambda objects.

But you clearly want `lambda_name<args>` to create some kind of callable expression, yes? You pass the result of that expression to a function. It can't be a member pointer or a function pointer, since neither of those can have template arguments or captured values. Therefore, it must be a functor. And since it is a different object of a different type from `lambda_name` (since you can call it without specifying the template parameters that `decltype(lambda_name)::operator()` requires), it must be a new functor.

Whether you consider it a "lambda object" or just a general functor is merely semantics. It's a value of a new type generated by the compiler.
 
Then a follow-up question is that why `functor<args>`
can't mean `functor.operator<args>`, and then we
realize that we miss lambda's "this" pointer here, but
since lambda's member functions' addresses are
compile time constants, thus they can be a part of
the type, so we only need to pass the `this` pointers
-- and that's the basic implementation strategy.
Going further, ref captures &p are not required to
be implemented as reference members, thus we
could just passing &p if that's the only capture, one
less indirections.

> [..] defining it inline makes your code less
> readable not more readable.
>

I just want to "express ideas directly in code"
(p0559r0 A.1, also from D&E). I mean function,
then give me a function.

What about the syntax "variable_name<args>" translates to "function" to you? I certainly don't see anything in that expression which to me would "mean function".

Meaning has to be expressed through syntax. And right now, syntax of the form "identifier<args>" represents an instantiation of a template. If `identifier` is a function template, then that expression results in a function (which, BTW, is not an object). If `identifier` is a variable template, then that expression results in a variable.

You want to take template instantiation syntax and turn it into something it isn't.

Zhihao Yuan

unread,
Sep 18, 2017, 10:18:26 PM9/18/17
to Nicol Bolas, ISO C++ Standard - Future Proposals, bastie...@gmail.com, Zhihao Yuan
On Mon, Sep 18, 2017 at 8:51 PM, Nicol Bolas <jmck...@gmail.com> wrote:
>
> Whether you consider it a "lambda object" or just a general functor is
> merely semantics. It's a value of a new type generated by the compiler.
>
> You want to take template instantiation syntax and turn it into something it
> isn't.

Yes, and, so? I've explained how this new kind of
entity works.

Template instantiation syntax creates dependent
entities, the entities used to be classes and
functions, in C++11 we added typedefs, and in
C++14 we added variables. Now I suggest to add
lambdas.

FrankHB1989

unread,
Sep 19, 2017, 12:52:02 AM9/19/17
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, bastie...@gmail.com, z...@miator.net


在 2017年9月19日星期二 UTC+8上午9:36:53,Nicol Bolas写道:
It would be strictly less readable when the original intention is "do not expect this is a named entity to be referenced elsewhere", for reason of separation of concerns.

This is a quite common scene, so adding extra comments for each cases would likely make readers confused. So keeping unnamed entities is the only proper implementation style.

Functions should be short; that makes it easy to read them and reason about them. All of that `if constexpr` logic takes up a lot of room. Room that's taking up space in your function that's, again, supposed to be short. By extracting all of that complex `if constexpr` code out into its own separate place, you make the function that uses it much more digestible.

Visual style does not make too much sense here. Will it be more readable if the function body is replaced by a single macro, unconditionally?

Code that takes too much room will be expensive to be read, physically. This is not only valid for functions.

bastie...@gmail.com

unread,
Sep 20, 2017, 3:08:48 AM9/20/17
to ISO C++ Standard - Future Proposals
Le mardi 19 septembre 2017 03:16:00 UTC+2, Richard Smith a écrit :. 
bool do_stuff(MyInt x) {
  return 7 + x < x(x + 1); // oops, your proposal parses this as a template argument list
}

Your implementation rejects this currently-valid code.
First of all thanks for your comment. 
This was actually an implementation error where I did't protect against error in the attempt to parse a template id. 
I've updated the compiler, your code now works as supposed : compiler explorer

As for the disambiguation, you're right and this effectively kills the proposal as without disambiguation the performance cost will be too great even I ignored the other issues.

That being said I still believe that simplifying the syntax call of the operator() to specify template parameter would be a good thing.
One reason that I failed to bring up for instance is if we want to override the deduced type of an argument.
For instance:
struct MyInt
{
   
MyInt(int);
   
operator int() const;
};

MyInt func();

[](auto&& x) { ... }.operator()<int>(func());
template<class T>
void do_x(T&& f)
{
     f.template operator()<int>(func());
}

So I thought about it and the simplest way I've found was by allowing :
postfix-expression . < template-parameter-listopt >
postfix
-expression -> < template-parameter-listopt >

It's simpler to implement (the patch is of 44 addition, 30 suppression and mostly indentation change due to new If), no possible conflict with operator<, no issue with dependent scope, no performance cost, no ambiguity, etc..
template<class T>
void do_x(T&& f)
{
     f
.<int>(func());
}

On top of that a function can't return an unresolved templated function and can't take one in parameter,
so making a templated function and a templated operator() be called the same way doesn't seem that interesting in the end.

Zhihao Yuan

unread,
Sep 20, 2017, 3:33:47 AM9/20/17
to std-pr...@isocpp.org
On Wed, Sep 20, 2017 at 2:08 AM, <bastie...@gmail.com> wrote:
> Le mardi 19 septembre 2017 03:16:00 UTC+2, Richard Smith a écrit :.
> template<class T>
> void do_x(T&& f)
> {
> f.<int>(func());
> }
>
> On top of that a function can't return an unresolved templated function and
> can't take one in parameter,
> so making a templated function and a templated operator() be called the same
> way doesn't seem that interesting in the end.

Oh no, please don't invent calling syntax. I don't
feel that this problem is that hard to solve. Lambda
are the only candidates we have to work on,
and when type is known to be a generic lambda, just
allow lambda<T...>; when it's not known, the type
is dependent, either ignore the use case, or ask for
template lambda<T...> to opt-in.

bastie...@gmail.com

unread,
Sep 20, 2017, 4:28:01 AM9/20/17
to ISO C++ Standard - Future Proposals, z...@miator.net
Le mercredi 20 septembre 2017 09:33:47 UTC+2, Zhihao Yuan a écrit :
Oh no, please don't invent calling syntax.  I don't
feel that this problem is that hard to solve.  Lambda
are the only candidates we have to work on,
and when type is known to be a generic lambda, just
allow lambda<T...>; when it's not known, the type
is dependent, either ignore the use case, or ask for
template lambda<T...> to opt-in. 
Both options are still on the table on my side.
The template prefix won't be difficult to implement on top of that and I'm working on it.
When I said killed I meant as is.

By the way can you clarify what lambda<T> without parenthesis would means for you ?
If it's simply lambda.operator()<T> then it is not allowed right now in C++ as one can't ask for a member pointer from an instance.
Would lambda<T> create a new lambda otherwise ?

Zhihao Yuan

unread,
Sep 20, 2017, 4:57:33 AM9/20/17
to bastie...@gmail.com, ISO C++ Standard - Future Proposals, Zhihao Yuan
On Wed, Sep 20, 2017 at 3:28 AM, <bastie...@gmail.com> wrote:
>
> By the way can you clarify what lambda<T> without parenthesis would means
> for you ?
> If it's simply lambda.operator()<T> then it is not allowed right now in C++
> as one can't ask for a member pointer from an instance.
> Would lambda<T> create a new lambda otherwise ?

Look at this: https://godbolt.org/g/Adh9kQ
I hard coded MF_f here, but a compiler can
certainly produce a template in scope to
encode a pointer-to-member function in type.
Also for bounded_method::operator(), a
compiler can just substitute in the correct
function call without going through perfect
forwarding.

lambda<T...> can actually be a thing --
an object of an unspecified fundamental
type encoding lambda<T...>::operator(),
and the object only needs to store a
&lambda. The idea can apply to non-
generic lambdas with captures as well --
lambda can produce this object by implicit
conversion, very similar to the non-capture
lambda to function pointer conversion,
except that the target type is unspecified,
so +lambda (unary operator+) become the
only way to retrieve this object. To sum
them up,

For non-generic lambdas:

+without_capture -> T0
T0 = Some (*)(T...)
+with_capture -> T1
T1 = unspecified<C, &C::operator()>

For generic lambdas:

lambda<T...> -> T2
T2 = unspecified<C, &C::operator()<T...>>

T0, T1, T2 are all fundamental types and
can be called with the same syntax
f(args, to, the, lambda) where f is the
corresponding object.

bastie...@gmail.com

unread,
Sep 20, 2017, 9:16:13 AM9/20/17
to ISO C++ Standard - Future Proposals, bastie...@gmail.com, z...@miator.net
Le mercredi 20 septembre 2017 10:57:33 UTC+2, Zhihao Yuan a écrit :
For non-generic lambdas:

  +without_capture -> T0
  T0 = Some (*)(T...)
  +with_capture -> T1
  T1 = unspecified<C, &C::operator()>

For generic lambdas:

  lambda<T...> -> T2
  T2 = unspecified<C, &C::operator()<T...>>

T0, T1, T2 are all fundamental types and
can be called with the same syntax
f(args, to, the, lambda) where f is the
corresponding object.

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


I like it but this can be done one the library-side, without my proposal, if we have an unconstrained template parameter (one that non-type, type, template,...).
template<class... Lambdas>
struct select : Lambdas...
{
   
using Lambdas::operator()...;

   
template<template/*class or auto for now*/... TemplateParams> /* by the way is there such a proposal, I haven't found any ? */
   
constexpr auto build() //or operator()() would also work
   
{
         
return [*this]<class... Args>(Args&&...)
         
{
             
return select::template operator()<TemplateParams...>(std::forward<Args>(args)...);
         
};
   
}
};

template<class... L>
select(L&&....) -> select<L...>;

select  writer =

{
   
[&fp]<buffer_policy x> (char* p, size_t sz)
   
{
        writeall
(fp, x | other_flags, p, sz);
       
// ...

   
},
   
[&fp]<buffer_policy x> (/*some other arguments*/)
   
{
     
//...
   
}
};

select writer  = []() {}; //doesn't work though, require braced-init-list

rsync_writer rs(writer.build<buffer_policy::unbuffered>());
 
// ...
stream_formatter fmt
(writer.build<buffer_policy::line_bufferred>());

Back to the proposal, how about forcing the use of the template keyword?
This would have the same benefits as the '.<...>()' syntax (even though a bit longer).
It would simplify a lot of the changes to the compiler (no more compile time cost).
If so we could allow calls to any unary operator afterwards instead of just operator()
for instance:
template i<int>; //i.template operator int(i);
template *i<int>; //i.template operator*<int>();
template i<int>(); //i.template operator()<int>();

By the way I've updated my compiler explorer.
The default compiler as the template prefix keyword update.
The second compiler implements the "x.<tparams...>(args...)" idea.

Zhihao Yuan

unread,
Sep 20, 2017, 3:59:06 PM9/20/17
to bastie...@gmail.com, ISO C++ Standard - Future Proposals, Zhihao Yuan
On Wed, Sep 20, 2017 at 8:16 AM, <bastie...@gmail.com> wrote:
>
> I like it but this can be done one the library-side, without my proposal, if
> we have an unconstrained template parameter (one that non-type, type,
> template,...).
> template<class... Lambdas>
> struct select : Lambdas...

No? You may have noticed that sizeof(select)
grows linearly w.r.t. the number of the number
of overloads, while in my example
sizeof(bounded_method) is constantly a
pointer's size.

And there is a usability problem to keep multiple,
isolated copies of the same member. Consider

[fd = 0]<int fcntl_flags, ...>

now one operator() instantiation modifies member
fd, the change is not reflected in another
instantiation, which is totally not the way a
member function should work.

> Back to the proposal, how about forcing the use of the template keyword?
> This would have the same benefits as the '.<...>()' syntax (even though a
> bit longer).

Traditionally and logically, template/typename
should not be "enforced". They should only
be necessary to disambiguate dependent names.

Richard Smith

unread,
Sep 21, 2017, 7:27:27 PM9/21/17
to std-pr...@isocpp.org
On 20 September 2017 at 00:08, <bastie...@gmail.com> wrote:
Le mardi 19 septembre 2017 03:16:00 UTC+2, Richard Smith a écrit :. 
bool do_stuff(MyInt x) {
  return 7 + x < x(x + 1); // oops, your proposal parses this as a template argument list
}

Your implementation rejects this currently-valid code.
First of all thanks for your comment. 
This was actually an implementation error where I did't protect against error in the attempt to parse a template id. 
I've updated the compiler, your code now works as supposed : compiler explorer

I'm afraid that doesn't solve the problem; slightly more elaborate example:

  return 7 + x < x(x + 1) && x > (4 + x) * 3;

Now "x<x(x+1) && x>(4 + x)" could be either a call to the lambda with template argument of x(x+1) && x, or some comparisons joined by an &&. compiler explorer
 
As for the disambiguation, you're right and this effectively kills the proposal as without disambiguation the performance cost will be too great even I ignored the other issues.

That being said I still believe that simplifying the syntax call of the operator() to specify template parameter would be a good thing.
One reason that I failed to bring up for instance is if we want to override the deduced type of an argument.
For instance:
struct MyInt
{
   
MyInt(int);
   
operator int() const;
};

MyInt func();

[](auto&& x) { ... }.operator()<int>(func());
template<class T>
void do_x(T&& f)
{
     f.template operator()<int>(func());
}

So I thought about it and the simplest way I've found was by allowing :
postfix-expression . < template-parameter-listopt >
postfix
-expression -> < template-parameter-listopt >

It's simpler to implement (the patch is of 44 addition, 30 suppression and mostly indentation change due to new If), no possible conflict with operator<, no issue with dependent scope, no performance cost, no ambiguity, etc..
template<class T>
void do_x(T&& f)
{
     f
.<int>(func());
}

On top of that a function can't return an unresolved templated function and can't take one in parameter,
so making a templated function and a templated operator() be called the same way doesn't seem that interesting in the end.

--
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.

bastie...@gmail.com

unread,
Sep 22, 2017, 5:43:59 AM9/22/17
to ISO C++ Standard - Future Proposals
Le vendredi 22 septembre 2017 01:27:27 UTC+2, Richard Smith a écrit :
I'm afraid that doesn't solve the problem; slightly more elaborate example:

  return 7 + x < x(x + 1) && x > (4 + x) * 3;

Now "x<x(x+1) && x>(4 + x)" could be either a call to the lambda with template argument of x(x+1) && x, or some comparisons joined by an &&. compiler explorer

The compile error you are seeing is actually yet another implementation issue (coming from the fact that there is no builtin way try-parse a template parameter list with gcc).

A true pitfall would be this for instance:
struct MyInt {
 
int value;
 
constexpr MyInt(int v) : value(v) {}
 
template<auto,class T> decltype(auto) constexpr operator()(T t) { return t; }
};

constexpr bool operator<(MyInt, auto&&) { return true; }
constexpr bool operator>(MyInt, auto&&) { return false; }
constexpr bool operator&&(auto&&, auto&&) { return true; }

constexpr auto do_stuff(MyInt x) {
 
return x < x && 1 >(x.value);
}

int main()
{
    do_stuff
(42) //with current standard false, with proposal 42
}


The supposed behavior is the following:

templateopt postfix-expression < template-parameter-listopt > ( expression-listopt )

If template is specified then only the following will be valid:
postfix-expression < template-parameter-listopt > ( expression-listopt )

If postfix-expression has a dependent type then template is required, otherwise '<' will be parsed as operator<.

If postfix-expression is not dependent,
if postfix-expression is non-scalar,
if its type has a templated operator() and doesn't have a member operator< (to avoid the cost of try-parse),
then try-parse 
postifx-expression.templateopt operator()< template-parameter-listopt > ( expression-listopt )
If at any point the try-parse fails (no such function, argument not constexpr, no such operator, etc...)
then return the parser at the '<' token and finish the postfix-expression (which will then trigger the evaluation of the '<' token as a less than sign later on).

In other word it's messy and costly.

The only fixes I see would be:
//template require at the front
template postfix-expression < template-parameter-listopt > ( expression-listopt )
//ex: template f<int>();

//template required before '<'
postfix
-expression template < template-parameter-listopt > ( expression-listopt )
//ex: f template<int>();

//the cleanest and shortest solution

postfix
-expression . < template-parameter-listopt >
postfix
-expression -> < template-parameter-listopt >
//ex: f.<int>();

I'd be interested to have your opinion on these.

bastie...@gmail.com

unread,
Sep 22, 2017, 9:13:14 AM9/22/17
to ISO C++ Standard - Future Proposals, bastie...@gmail.com, z...@miator.net


Le mercredi 20 septembre 2017 21:59:06 UTC+2, Zhihao Yuan a écrit :
On Wed, Sep 20, 2017 at 8:16 AM,  <bastie...@gmail.com> wrote:
>
> I like it but this can be done one the library-side, without my proposal, if
> we have an unconstrained template parameter (one that non-type, type,
> template,...).
> template<class... Lambdas>
> struct select : Lambdas...

No?  You may have noticed that sizeof(select)
grows linearly w.r.t. the number of the number
of overloads, while in my example
sizeof(bounded_method) is constantly a
pointer's size.


I used capture by copy because it is dangling-pointer-safe but you can do the same by reference/pointer if it's storage that matters.

#include <utility>
#include <type_traits>
#include <functional>
#include <iostream>

template<class Lambda, bool IsEmpty = (sizeof(Lambda) <= 1)>
struct select_reference
{
 
Lambda *l;
 
constexpr select_reference(Lambda &l) : l(std::addressof(l)) {}

 
template<class... Args>
 
constexpr auto build()
 
{
     
return [&]<class... Rs>(Rs&&... args)
     
{
       
return l->template operator()<Args...>(std::forward<Rs>(args)...);
     
};
 
}
};

template<class Lambda>
struct select_reference<Lambda, true>
{
 
constexpr select_reference(Lambda&) {}

 
template<class... Args>
 
constexpr auto build()
 
{
   
return []<class... Rs>(Rs&&... args)
   
{
     
return static_cast<std::add_pointer_t<Lambda>>(nullptr)->
       
template operator()<Args...>(std::forward<Rs>(args)...);
     
};
 
}
};

template<class T>
select_reference
(T&) -> select_reference<T, (sizeof(T) <= 1)>;

int main()
{
 
auto lambda = []<class T>() {
    std
::cout << sizeof(T) << std::endl;
 
};
 select_reference s
= lambda;

 
static_assert(sizeof(s) <= 1);

 
[](auto&& f)
 
{
   
static_assert(sizeof(f) <= 1);
    f
();
 
}(s.build<int>());
}

The version with multiple ones is a bit more tricky but here it is : https://godbolt.org/g/5wpMq1.

 
And there is a usability problem to keep multiple,
isolated copies of the same member.  Consider

  [fd = 0]<int fcntl_flags, ...>

now one operator() instantiation modifies member
fd, the change is not reflected in another
instantiation, which is totally not the way a
member function should work.

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

You simply add another member function to build by reference instead.
template<class... Lambdas>
struct select : Lambdas...
{
   
using Lambdas::operator()...;

   
template<template/*class or auto for now*/... TemplateParams> /* by the way is there such a proposal, I haven't found any ? */

   
constexpr auto build_copy() //or operator()() would also work

   
{
         
return [*this]<class... Args>(Args&&...)
         
{
             
return select::template operator()<TemplateParams...>(std::forward<Args>(args)...);
         
};
   
}


   
template<template... TemplateParams>
   
constexpr auto build_reference()
   
{
         
return [this]<class... Args>(Args&&...)

Zhihao Yuan

unread,
Sep 22, 2017, 9:52:54 AM9/22/17
to bastie...@gmail.com, ISO C++ Standard - Future Proposals, Zhihao Yuan
On Fri, Sep 22, 2017 at 8:13 AM, <bastie...@gmail.com> wrote:
>> You may have noticed that sizeof(select)
>> grows linearly w.r.t. the number of the number
>> of overloads, while in my example
>> sizeof(bounded_method) is constantly a
>> pointer's size.
>>
>
> I used capture by copy because it is dangling-pointer-safe but you can do
> the same by reference/pointer if it's storage that matters.
>

......I ask you to notice the size difference not
because I want you to fix the size difference,
but hope you can understand the semantics
differences. I wrote 2 paragraphs, and they
were talking about exactly the same thing,
that is, given

auto f = [fd=0]<...> {};

, f<v1>() operates on fd, and f<v2>() operates
on the same fd, while in

overload([fd] {...1}, [fd] {...2})

, there are 2 fds. Changing how `overload`
taking lambdas, or changing how you build
the call, have no effect, because of the
simple fact, that is those solutions start
from having multiple lambdas, therefore
they operate on multiple capture lists, while
in a generic lambda, there is only one
capture list.

bastie...@gmail.com

unread,
Sep 22, 2017, 11:25:47 AM9/22/17
to ISO C++ Standard - Future Proposals, bastie...@gmail.com, z...@miator.net
I got that but I don't see why you're bringing that up.
I've only brought override/select to show that your idea is implementable using a know c++17 pattern by simply adding a method and with even more capabilities.
My point was that having implicit lambdas to other local lambdas by reference being created only seems like a bad idea.
In the use case you presented it worked but it might cause dangling pointer in many others.

Zhihao Yuan

unread,
Sep 22, 2017, 2:06:19 PM9/22/17
to bastie...@gmail.com, ISO C++ Standard - Future Proposals, Zhihao Yuan
On Fri, Sep 22, 2017 at 10:25 AM, <bastie...@gmail.com> wrote:
> I've only brought override/select to show that your idea is implementable
> using a know c++17 pattern by simply adding a method and with even more
> capabilities.

And I've explained why that doesn't work, because
that started from having multiple lambdas, therefore
they operate on multiple capture lists.

> My point was that having implicit lambdas to other local lambdas by
> reference being created only seems like a bad idea.
> In the use case you presented it worked but it might cause dangling pointer
> in many others.

Don't you think [&] capture is an easier way
to create dangling pointers? To use a feature
requires understanding it. For this case, from
my point of view, reference semantics is a
good enough default,
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0119r2.pdf
has the same decision on the question about
how to lift member access. And if people want
a copy of the lambda, we can find a syntax to
do it:

return *lambda<args>;

Richard Smith

unread,
Sep 22, 2017, 4:18:26 PM9/22/17
to std-pr...@isocpp.org
On 22 September 2017 at 02:43, <bastie...@gmail.com> wrote:
Le vendredi 22 septembre 2017 01:27:27 UTC+2, Richard Smith a écrit :
I'm afraid that doesn't solve the problem; slightly more elaborate example:

  return 7 + x < x(x + 1) && x > (4 + x) * 3;

Now "x<x(x+1) && x>(4 + x)" could be either a call to the lambda with template argument of x(x+1) && x, or some comparisons joined by an &&. compiler explorer

The compile error you are seeing is actually yet another implementation issue (coming from the fact that there is no builtin way try-parse a template parameter list with gcc).

FWIW, I disagree that this is just an implementation issue. C++'s disambiguation rules are based on syntax and name lookup results, not on whether certain operator invocations satisfy the semantic rules (and there are sound reasons for that, primarily to permit templates to be parsed). If you're accepting the above because "x(x+1) && x" happens to not find a suitable overloaded operator&&, then you're not playing by the normal rules. (Of course, we do get to change those rules if the change is sound and there's sufficient justification.)
At the previous committee meeting, we discussed allowing

  template identifier < template-argument-list > ( expression-list[opt] )
  template operator-function-id < template-argument-list > ( expression-list[opt] )
  template literal-operator-id < template-argument-list > ( expression-list[opt] )

as a special kind of postfix-expression, to allow ADL-only calls to templated functions for cases like:

namespace Foo {
  struct X {};
  template<typename T> T cast_to(X);
}
int f(Foo::X x) {
  return template cast_to<int>(X);
}

... but it turned out that we could actually avoid the use of the 'template' keyword here altogether with a slight tweak of the rules:

 * If an unqualified-id is followed by a <, it is looked up to determine if it names a template. If the lookup finds any functions or templates, the < introduces a template argument list. Otherwise, if the lookup succeeds, the < is interpreted as a comparison. Otherwise, the < introduces a template argument list.

(The bold parts are the tweaks.) The only existing valid cases for which this changes the meaning are cases where a function name appears and is decayed to a pointer in a location that syntactically immediately precedes a < operator. EWG seemed happy to change the meaning of those cases to avoid the need for a 'template' keyword, on the basis that they're exceptionally rare. Example broken code:

bool is_function_pointer_size_less_than(size_t n) {
  return sizeof +is_function_pointer_size_less_than < n;
}

The reason I bring this up is: there appears to be sufficient distaste within EWG for adding more contexts in which a "template" disambiguator is required that they're prepared to break existing code to avoid it. So I think the first option is likely to not make the cut. The second option suffers from the same problem and also uses 'template' in a way that is inconsistent with the rest of the language.

The third and fourth options have some appeal, but I don't think we should introduce a new disambiguator for < just for this one construct. Are you aware of the rust disambiguator for template arguments? They use

  f::<...>(...)

in [most, but not all] contexts where < could mean either a comparison or template arguments. That kind of syntax (accepted in any case where a template argument list begins) might be worth proposing as a general improvement to C++.

Ville Voutilainen

unread,
Sep 22, 2017, 4:27:26 PM9/22/17
to ISO C++ Standard - Future Proposals
On 22 September 2017 at 23:18, Richard Smith <ric...@metafoo.co.uk> wrote:
> The reason I bring this up is: there appears to be sufficient distaste
> within EWG for adding more contexts in which a "template" disambiguator is
> required that they're prepared to break existing code to avoid it. So I
> think the first option is likely to not make the cut. The second option
> suffers from the same problem and also uses 'template' in a way that is
> inconsistent with the rest of the language.


I'm not sure about that. I get a tingling sense that there's indeed
such a distaste for 'typename',
but I don't have a good grasp on where that group stands on
'template'. Personally, I have,
some years ago, suggested that we should have a simple rule, which is
"require 'typename'
or 'template' when need be, be lax about them when they are
superfluous", but it seems
as if the current trend is against at least the first part. I am
fairly happy with that direction,
because it makes sense for programmers; when I made that suggestion I
refer to, I was
fairly Core-ish in my thinking. I have since Evolved.
Reply all
Reply to author
Forward
0 new messages