Sidenote. Some will argue the problem is most correctly solved by introducing new type(s).
In that particular case this might be true, but that is not always the case - often a new type is not the best solution,
either because the type would be completely superficial or because it is not feasible to introduce and maintain dozens of types, not used outside a limited context.
Although we could simply create instances of the label type, this is far from perfect and not what the established practice is currently.
Default labels will always look awkward, besides, one can still construct{} it.
Taking the address, for whatever reason, can be done using std::addressof, which itself is a function.
The syntax case label: is not allowed as it is not needed and makes it easier to prevent any collision with switch statement labels.
This "quirk" can be something positive as this will aid teaching the fact, labels are just arguments.
Not only that, but some might as well preferer using the comma for all argument passing.
Lastly, if we are to ever have named arguments this can be used to be extra clear, we are passing a label argument, not naming a regular argument.
The only exception is specializing for the stl (like std::allocator) or specializing the stl itself (like introducing new execution policies).
We saw that the first case works as expected - one just passes the std-provided label object to mark the custom allocator.
The second case is a bit more tricky and might force the user to do an explicit call to the label argument to prevent the std one being picked by default.
This can be trivially resolved by providing a user-facing interface in non-std namespace, which delegates the call to the correctly qualified std function
It is hard to tell if such a lookup will make finding the function itself harder or easer - after all, the fact that the compiler knows the argument is label one, dramatically can limit the overlanding set,
no matter in which namespace is either the function or the label argument. In a way, all labels are as if a subclass of just one parent class, culling based on that should help lookup.
It should be noted, migrating the stl to labels is trivial - it literally means two lines of code for every function using a tag. One to introduce a case struct, reusing the old name, and one to create a forwarding overload.
Doing this will give us all the benefits of using labels - association b/w declaration and invocation, better lookup rules and more expressive syntax.
This call site syntax looks like saying the first argument
has value 5.7
Besides, regardless of what can be done for generic
code, “named constructors” should at least support
the Point::cartesian(5.7, 1.2) syntax.
--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________
This call site syntax looks like saying the first argument
has value 5.7
Besides, regardless of what can be done for generic
code, “named constructors” should at least support
the Point::cartesian(5.7, 1.2) syntax.
On Monday, July 16, 2018 at 5:43:48 PM UTC+3, Zhihao Yuan wrote:This call site syntax looks like saying the first argument
has value 5.7Few ways around this.First and foremost, people will get used to the multiple use of label arguments -in one case they label the function itself, like in parallel stl cases,in other they label an argument, like the rect(bottomLeft: point, size)caseand often they label multiple arguments like the case here and all "in-place"-like constructors in std.The designer is also free to accept std::pair instead, forcing a more explicit call - Point(cartesian: {5.7, 1.2});or, in more complex cases, he can add another label to break parameter "packs".Lastly, one is free to use a comma after the label - Point(cartesian:, 5.7, 1.2); so to be more specific.And don't forget Point::cartesian() is not any better! Not by a long shot.If this is to be done right it should be following a convention like the Qt's "from" one - fromCartesian(. . .)- just a floating static function with the name 'cartesian' means little without documentation.At least as label the user can deduce, it is not about the first variable as "a cartesian float" does not mean anything, neither does "polar float".Also, in reality "cartesian" should be implicit. The user will ever have to specify only "polar: . . .", as it is unusual.
Besides, regardless of what can be done for generic
code, “named constructors” should at least support
the Point::cartesian(5.7, 1.2) syntax.Static functions used as named constructors don't work with template argument deduction (along with many other downsides) and we should move away from them completely.The fact we teach and use this idiom for 30 years does not make it a good one - a static function in no way implies construction.
Static functions have no benefits besides written text to the user, something label arguments are capable of doing, not only that, but the text can move around the arguments.Yes the user will need to learn to read labels, but chances are, one will be to deduce the meaning from context, much like one can, most of the time, deduce which static functions are "constructors".
--
Zhihao Yuan, ID lichray
The best way to predict the future is to invent it.
_______________________________________________
From: mihailn...@gmail.com <mihailn...@gmail.com>
Sent: Monday, July 16, 2018 7:25 AMPoint p1 = Point(cartesian: 5.7, 1.2);
Point p2 = Point(polar: 5.7, 1.2);
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/cfef3371-ce08-4e72-97ad-4328333be859%40isocpp.org.
I had never said that this syntax is static functions, I only
said that your feature should be able to be used with that
syntax.
namespace wrapper { namespace core {
template<class T>
struct adopted_pointer
{
adopted_pointer(T *p) noexcept
: ptr_(p)
{
}
adopted_pointer(adopted_pointer const& other) = delete;
constexpr adopted_pointer(adopted_pointer&& other) noexcept
: ptr_(other.ptr_)
{
other.ptr_ = nullptr;
}
adopted_pointer& operator =(adopted_pointer&& other) = delete;
adopted_pointer& operator =(adopted_pointer const&& other) = delete;
~adopted_pointer() noexcept
{
assert(!ptr_);
}
T *get()&&
{
auto result = ptr_;
ptr_ = nullptr;
return result;
}
T *ptr_;
};
namespace wrapper {
using ::wrapper::core::adopted_pointer;
template<class T>
auto adopt(T *p) -> adopted_pointer<T>
{
return {p};
}
}
auto ps = reinterpret_cast<char*>(std::malloc(100));
std::strcpy(ps, "hello, world");
auto ds = wrapper::core::dynamic_c_string(wrapper::adopt(ps));
auto ds = wrapper::core::dynamic_c_string(adopt: ps);
Basically, with "pathname" identifier lookup syntax, you get get the effect of this proposal, except that you need to do `{},` after such tags. The OP seems to think that this is not good enough, that we really need to disguise the fact that an argument is an argument, so the more general mechanis is unlikely to be pursued by them.
auto ds = wrapper::core::dynamic_c_string(adopt:, ps);
auto ds = wrapper::core::dynamic_c_string(adopt: ps);
On Thursday, July 19, 2018 at 4:57:48 AM UTC-4, Richard Hodges wrote:
Sorry for the top post. Perhaps the group could let me know if this is worthy of a separate thread.
...
I mentioned something to this effect on another thread. It was essentially the ability to deduce the full "pathname" of an identifier from the location where that identifier gets used. Such syntax would be useful in a number of places.Switching over an `enum class` is a place where people often find it annoying to have to re-type `EnumName::` before the enumerators, even though the compiler knows statically where the enumerator comes from. Now obviously you can't use `identifier:` for that syntax, since `case` statements are terminated by `:` characters. But the principle is meaningful.
--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ff08a552-4ef9-4115-9de2-56850de26da7%40isocpp.org.
On Thu, 19 Jul 2018 at 18:09, Nicol Bolas <jmck...@gmail.com> wrote:On Thursday, July 19, 2018 at 12:00:51 PM UTC-4, Nicol Bolas wrote:Basically, with "pathname" identifier lookup syntax, you get get the effect of this proposal, except that you need to do `{},` after such tags. The OP seems to think that this is not good enough, that we really need to disguise the fact that an argument is an argument, so the more general mechanis is unlikely to be pursued by them.I should correct this. Because of inline variables, such syntax would not even need `{}`. So armed with such syntax (which we can debate), you would be able to do this, where `adopt` is an inline variable:
auto ds = wrapper::core::dynamic_c_string(adopt:, ps);as opposed to the OP's idea of this:
auto ds = wrapper::core::dynamic_c_string(adopt: ps);That is, with the OP's idea, you get to drop a comma. With mine, you have to use a comma and a variable template, but you solve a bunch of other problems too.Hmm. How about:auto ds = wrapper::core::dyanmic_c_string(.adopt=ps);
Can anyone pinpoint a language which sports both function overloading on type and named parameters at call sites (without help from declarations)? That would be an interesting source of inspiration rather than Python's untyped, unoverloadable functions.
func add(a: Int, to b: Int) -> Int {
return a + b
}
func add(a: Int, and b: Int) -> Int {
return a * b
}
let answer1 = add(2, to: 4) // 6
let answer2 = add(2, and: 4) // 8
I was thinking about this one: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/O6rLutWGp4ISo let me tell you what I was thinking:There exist a common, yet unutterable, where tag types live (lets call it tag_ns to simplify).When you declare a function with tags:The compiler creates a tag type in this namespace called tag_name, and transform the declaration like that:
int foo(tag_name: float f);
int foo(tag_ns::tag_name, float f);When you call a function with a tag:
foo(tag_name: 1.f);The compiler creates a tag type in this namespace called tag_name, replaces the expression with a call like that (without even needing to know the declaration of the function):
foo(tag_ns::tag_name{}, 1.f);
If two functions (even in different namespaces) use the same tag name, they will have the same tag type, which is fine.
...The main feature here would be the automatic introduction of types. A bit like a template instantiation. It is implicit.
In your first proposal, you mentioned you wanted to be able to do something like that:I don't think it is useful. you are not naming an argument here.
any(in_place<string>: "");
Now, to be fair, you could do something equivalent in C++20:
int foo(std::tag<"tag_name">, float f);
foo("tag_name"tag, 1.f);But the shorthand syntax would be very nice.Moreover, such a shorthand syntax would give people incentives to use it (I consider it a good thing).Let's make a quick poll. Which one do you prefer? (try to see it as a newcomer to the language)
std::find_if(v.begin(), v.end(), functor);
std::find(v.begin(), v.end(), "if"tag, functor);
std::find(v.begin(), v.end(), if: functor);
I personally prefer the third one (I would like it even more with ranges).This is not really about the possibility to do it now, but how the syntax looks.Overall I want to clarify: that's my opinion, and I'm pretty sure some will disagree, but I really think it is worth pursuing.
I was thinking about this one: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/O6rLutWGp4ISo let me tell you what I was thinking:There exist a common, yet unutterable, where tag types live (lets call it tag_ns to simplify).When you declare a function with tags:The compiler creates a tag type in this namespace called tag_name, and transform the declaration like that:
int foo(tag_name: float f);
int foo(tag_ns::tag_name, float f);When you call a function with a tag:
foo(tag_name: 1.f);The compiler creates a tag type in this namespace called tag_name, replaces the expression with a call like that (without even needing to know the declaration of the function):
foo(tag_ns::tag_name{}, 1.f);
If two functions (even in different namespaces) use the same tag name, they will have the same tag type, which is fine.Yes, initially I was thinking names are names no matter the namespace, but I abandoned this.I abandoned it because tags are types, and are used for overload resolution. If the std has a tag named 'par' and I want to use this name but with different implementation, I must be able to do that, much like todayWe need namespaces for tags, and, to the very least labels should be defined in the namespace of the functions.
void foo(tag:);
void bar(tag:);
But then I also abended automatic introduction...
...The main feature here would be the automatic introduction of types. A bit like a template instantiation. It is implicit.Automatic introduction is not that good of a barging - we invent new rules to save some typing, but new problems arise.
Where are tags created? How can we make them template? How can we add attributes to them? Is this really new signature?
Sidenote, how do you name the type to create a function signature? decltype(name:)? name: to mean different things in different contexts? Things escalate quickly.
void foo(tag_name:);
// or
// void foo(std::tag<"tag_name">);
In the end, I made it all as conservative as possible and as much based on current practice as possible. The initial simplicity was deceiving, because it was just a morning idea.
This is not to say we could not use some shortcut syntax.We couldint foo(tag_name: float f);generatecase struct tag_name; //< enclosing scopeint foo(case tag_name, float f);But I still question the pros/cons ratio.In your first proposal, you mentioned you wanted to be able to do something like that:I don't think it is useful. you are not naming an argument here.
any(in_place<string>: "");In most cases tags are used it is not as if the argument is named.
Le jeudi 2 août 2018 17:23:06 UTC+2, mihailn...@gmail.com a écrit :I was thinking about this one: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/O6rLutWGp4ISo let me tell you what I was thinking:There exist a common, yet unutterable, where tag types live (lets call it tag_ns to simplify).When you declare a function with tags:The compiler creates a tag type in this namespace called tag_name, and transform the declaration like that:
int foo(tag_name: float f);
int foo(tag_ns::tag_name, float f);When you call a function with a tag:
foo(tag_name: 1.f);The compiler creates a tag type in this namespace called tag_name, replaces the expression with a call like that (without even needing to know the declaration of the function):
foo(tag_ns::tag_name{}, 1.f);
If two functions (even in different namespaces) use the same tag name, they will have the same tag type, which is fine.Yes, initially I was thinking names are names no matter the namespace, but I abandoned this.I abandoned it because tags are types, and are used for overload resolution. If the std has a tag named 'par' and I want to use this name but with different implementation, I must be able to do that, much like todayWe need namespaces for tags, and, to the very least labels should be defined in the namespace of the functions.
The tag has no implementation, only the overload using the tag has an implementation. So there is absolutely no problem here.
void foo(tag:);
void bar(tag:);Why tag in both cases couldn't be the same? We use nothing from those tag objects.
How many real world code use non-empty classes as tags?
But then I also abended automatic introduction......The main feature here would be the automatic introduction of types. A bit like a template instantiation. It is implicit.Automatic introduction is not that good of a barging - we invent new rules to save some typing, but new problems arise.What are those problems ?If tag: refers to a single type wherever you are (even across TUs), then what is the problem?Where are tags created? How can we make them template? How can we add attributes to them? Is this really new signature?First, I was thinking in an unutterable namespace, yet common to all TUs.But then, making them an instantiation of std::tag<"tag_name"> might be better.You don't add attributes to them because you don't need to. Do you add attributes to the definition of std::string?Sidenote, how do you name the type to create a function signature? decltype(name:)? name: to mean different things in different contexts? Things escalate quickly.
When you declare a function, you would do:
void foo(tag_name:);
// or
// void foo(std::tag<"tag_name">);That's true name: would have different meaning depending on the context, but that's would be the case anyway because of labels.
And actually, when declaring a function and when using it, they don't mean the same language construct, but still have a common meaning: they are tags.So only the translation would be different, not the meaning.Actually, with decltype(name:), whatever the translation of name: would be (type or object of that type), the result is still the same.In the end, I made it all as conservative as possible and as much based on current practice as possible. The initial simplicity was deceiving, because it was just a morning idea.On the contrary, the initial simplicity was great. I think you shouldn't have tried to solve its issues by making more complex, but on the contrary making it more simple (like I did).
This is not to say we could not use some shortcut syntax.We couldint foo(tag_name: float f);generatecase struct tag_name; //< enclosing scopeint foo(case tag_name, float f);But I still question the pros/cons ratio.In your first proposal, you mentioned you wanted to be able to do something like that:I don't think it is useful. you are not naming an argument here.
any(in_place<string>: "");In most cases tags are used it is not as if the argument is named.
True, tags are not names.Avoiding this use case makes the design very simple.Much simpler than all the alternatives you tried.
On Thursday, August 2, 2018 at 6:58:01 PM UTC+3, floria...@gmail.com wrote:
Le jeudi 2 août 2018 17:23:06 UTC+2, mihailn...@gmail.com a écrit :I was thinking about this one: https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/O6rLutWGp4ISo let me tell you what I was thinking:There exist a common, yet unutterable, where tag types live (lets call it tag_ns to simplify).When you declare a function with tags:The compiler creates a tag type in this namespace called tag_name, and transform the declaration like that:
int foo(tag_name: float f);
int foo(tag_ns::tag_name, float f);When you call a function with a tag:
foo(tag_name: 1.f);The compiler creates a tag type in this namespace called tag_name, replaces the expression with a call like that (without even needing to know the declaration of the function):
foo(tag_ns::tag_name{}, 1.f);
If two functions (even in different namespaces) use the same tag name, they will have the same tag type, which is fine.Yes, initially I was thinking names are names no matter the namespace, but I abandoned this.I abandoned it because tags are types, and are used for overload resolution. If the std has a tag named 'par' and I want to use this name but with different implementation, I must be able to do that, much like todayWe need namespaces for tags, and, to the very least labels should be defined in the namespace of the functions.The tag has no implementation, only the overload using the tag has an implementation. So there is absolutely no problem here.
void foo(tag:);
void bar(tag:);Why tag in both cases couldn't be the same? We use nothing from those tag objects.Tags are used for specialization of functions (parallel library is an example). I must be able to declare tags that are named the same as the std ones, yet are different type and will pick my overload, not the std one.
If we completely remove namespaces for tags people will be forced to go the C-way of naming to be able to create new type under the "same name" - cuda_parallel - to force new overload.
foo(cuda::parallel: ...);
foo(cuda::parallel, ...);
How many real world code use non-empty classes as tags?But then I also abended automatic introduction......The main feature here would be the automatic introduction of types. A bit like a template instantiation. It is implicit.Automatic introduction is not that good of a barging - we invent new rules to save some typing, but new problems arise.What are those problems ?If tag: refers to a single type wherever you are (even across TUs), then what is the problem?Where are tags created? How can we make them template? How can we add attributes to them? Is this really new signature?First, I was thinking in an unutterable namespace, yet common to all TUs.But then, making them an instantiation of std::tag<"tag_name"> might be better.You don't add attributes to them because you don't need to. Do you add attributes to the definition of std::string?Sidenote, how do you name the type to create a function signature? decltype(name:)? name: to mean different things in different contexts? Things escalate quickly.When you declare a function, you would do:
void foo(tag_name:);
// or
// void foo(std::tag<"tag_name">);That's true name: would have different meaning depending on the context, but that's would be the case anyway because of labels.And what about std::function and pointers to functions? We will need to touch a lot of places to enable this syntax.
int main() {
// Parameter list of a function declarator
int foo(name: int); // -> int foo(std::tag<"name">, int);
std::function<int(name: int)> f = foo; // -> std::function<int(std::tag<"name">, int)> f = foo;
int (*g)(name: int) = static_cast<int(*)(name: int)>(&foo); // -> int (*g)(std::tag<"name">, int) = static_cast<int(*)(std::tag<"name">, int)>(&foo);
using foo_t = int(name: int); // -> int(std::tag<"name">, int)
// call expression
foo(name: 1); // -> foo(std::tag<"name">{}, 1);
// decltype expression
decltype(name:) h; // -> std::tag<"name"> h;
}
Also note that we will need extra rules what introduces and what just names a label. And these are not trivial, we can't always blindly introduce.
Makes no sense to introduce a type by declaring a function pointer, even if we agree to not have namespaces for them.
And actually, when declaring a function and when using it, they don't mean the same language construct, but still have a common meaning: they are tags.So only the translation would be different, not the meaning.Actually, with decltype(name:), whatever the translation of name: would be (type or object of that type), the result is still the same.In the end, I made it all as conservative as possible and as much based on current practice as possible. The initial simplicity was deceiving, because it was just a morning idea.On the contrary, the initial simplicity was great. I think you shouldn't have tried to solve its issues by making more complex, but on the contrary making it more simple (like I did).It is more simple, in the sense, it maps 100% on to established practice and 95+% on current types, declarations, scope, etc, rules
This is not to say we could not use some shortcut syntax.We couldint foo(tag_name: float f);generatecase struct tag_name; //< enclosing scopeint foo(case tag_name, float f);But I still question the pros/cons ratio.In your first proposal, you mentioned you wanted to be able to do something like that:I don't think it is useful. you are not naming an argument here.
any(in_place<string>: "");In most cases tags are used it is not as if the argument is named.True, tags are not names.Avoiding this use case makes the design very simple.Much simpler than all the alternatives you tried.Why should I avoid this case, I don't understand? The colon is perfectly deducible in different contexts to mean slightly different things, but it always stands for a clarification.
If we completely remove namespaces for tags people will be forced to go the C-way of naming to be able to create new type under the "same name" - cuda_parallel - to force new overload.What do you gain with:over:
foo(cuda::parallel: ...);
foo(cuda::parallel, ...);?If it is only to be able to put a colon here, then it is not worth the effort.
Also, I really don't like the three colons.
You also need to define the class template std::tag, but that will be trivial.Also note that we will need extra rules what introduces and what just names a label. And these are not trivial, we can't always blindly introduce.Makes no sense to introduce a type by declaring a function pointer, even if we agree to not have namespaces for them.If you need a tag, you need a tag even when declaring a function pointer.And yes, we can blindly introduce tag types. How is it different from template instatiation?