Closed msadeqhe closed 1 year ago
You should mark the this
parameter as inout
to express your intent of mutating the class. So the member function definition will look like this:
mutableCall : (inout this) = { this.a = "OOPE!"; }
Thanks. You're right, and it works as intended now.
By the way CPP2 should output a compilation error message.
Yes, a compilation message should be emitted. I guess it's because of the reason that Herb is not done implementing classes yet. Or is he? 👀
Currently, classes still need to be finished. Herb wrote in the commit (https://github.com/hsutter/cppfront/commit/258190ef6df8db8bc31d0eb5d94094da5d800623) message:
Added initial basic support.
Includes:
- access-specifiers: public, protected, private
- defaults: in type scope (aka members), types and functions are public by default, objects (data members) are private by default
- this-specifiers: implicit, virtual, override, final (but can't use them much since we don't have inheritance syntax yet, see next list)
- explicit `this` parameters (where the this-specifiers go)
NOT included yet:
- special member functions (`operator=`)
- inheritance / base classes
- metaclass functions to apply checks, defaults, and generated code (that'll be last...)
I have been playing with it, and I explored how to use it:
N : namespace = {
t : type = {
i : int;
public j : int = 1;
protected k : std::pair<int,int> = (1,2);
f0: () = {
std::cout << "f0" << std::endl;
}
private f1: (this) = {
std::cout << "f1" << std::endl;
}
protected f2: (virtual this) = {
std::cout << "f2" << std::endl;
}
// f3: (override this) = {
// std::cout << "f3" << std::endl;
// }
f4: (implicit this) = {
std::cout << "f4" << std::endl;
}
// f5: (final this) = {
// std::cout << "f5" << std::endl;
// }
f6: (in this) = {
std::cout << "f6" << std::endl;
}
f7: (inout this) = {
std::cout << "f7" << std::endl;
}
// f8: (out this) = {
// std::cout << "f8" << std::endl;
// }
f9: (move this) = {
std::cout << "f9" << std::endl;
}
}
}
Generates:
namespace N {
class t {
private: int i;
public: int j {1};
protected: std::pair<int,int> k {1, 2};
public: static auto f0() -> void{
std::cout << "f0" << std::endl;
}
private: auto f1() const -> void{
std::cout << "f1" << std::endl;
}
protected: virtual auto f2() const -> void{
std::cout << "f2" << std::endl;
}
// f3: (override this) = {
// std::cout << "f3" << std::endl;
// }
public: auto f4() const -> void{
std::cout << "f4" << std::endl;
}
// f5: (final this) = {
// std::cout << "f5" << std::endl;
// }
public: auto f6() const -> void{
std::cout << "f6" << std::endl;
}
public: auto f7() -> void{
std::cout << "f7" << std::endl;
}
// f8: (out this) = {
// std::cout << "f8" << std::endl;
// }
public: auto f9() && -> void{
std::cout << "f9" << std::endl;
}
};
};
Commented sections are the ones that do not work yet.
We have cv-qualifiers and ref-qualifiers for member functions in C++, instead we have in
, inout
, out
, forward
, move
and copy
in CPP2.
In C++ the following combinations of qualifiers are available (ignore volatile
), and according to function parameters in CPP2, I write the corresponding this
parameter for them in CPP2:
class X {
auto func_without_qualifiers() { ... } // copy this
auto func_ref() & { ... } // inout this
auto func_rref() && { ... } // move this
auto func_const() const { ... } // copy this
auto func_ref_const() const& { ... } // in this
auto func_rref_const() const&& { ... } // ???
};
But your test examples have different results, does CPP2 preserve overload resolution semantic of C++?
@msadeqhe in P0708 Parameter passing
section 1.7 Segue: The this parameter
Herb describes the following:
For member functions, the this parameter is implicit and so can’t be qualified using the same syntax as other parameters, and so the C++ convention is to express qualifiers on the this parameter via special additional grammar at the end of the member function parameter list. Declarative parameter passing for this would then go in the same place as all the other this qualifiers:
// qualifying “this” on member functions
auto f() in {}. // present me an X I can read from
auto f() inout {}. // present me an X I can read from and will write to
auto f() out {}. // present me an X I will assign to
auto f() move {}. // present me an X I will move from
auto f() forward {} // present me an X I will pass along
Note how these correspond to, and subsume and extend, the existing member function qualifications:
Passing this object... |
Similar to today's... | Notes |
---|---|---|
auto X::f() in |
auto X::f() const |
in additionally passes *this by value when that is cheaper |
auto X::f() inout |
auto X::f() |
Today’s existing “mutable” default |
auto X::f() out |
X::X (constructor) |
See §2 |
auto X::f() move |
auto X::f() && |
move additionally guarantees that if the function returns normally, this object is known to be moved-from |
auto X::f() forward |
n/a |
Today there is no way to forward this object |
The syntax is not the same but the meaning is similar.
I don't know what implicit
keyword means. @hsutter can you clarify?
Commented sections are the ones that do not work yet.
Note that override
and final
do "work" in that they emit the correct Cpp1 code, but because I haven't implemented inheritance yet you can't actually legally exercise them. (Note that final
implies override
.)
And out this
on a non-operator=
function is disabled because the only way for it to be useful would be to allow calling the function on an uninitialized object. I may allow that extension, but it seems a bit novel and I want to see actual motivating use cases down the road before spending time allowing and teaching it.
I don't know what
implicit
keyword means. @hsutter can you clarify?
It's the opposite of explicit
. Constructors will be explicit by default, and implicit
is the opt-in to allow implicit conversions to this type.
Note: The next commit will have more checking, to allow implicit
only on an out this
parameter of an operator=
function (a constructor).
Thank you for the explanation! implicit
is a clear and probably long-awaited feature.
I wasn't precise about override
and final
- they do work on the cppfront side, but as there is no inheritance yet, we can't override
a virtual method from the base class, and we cannot make it final
.
@msadeqhe Also note that the intentional parameter passing in Cpp2 doesn't have to cover all the options Cpp1 allows today, because today's approach is low-level and allows combinations that don't make sense. You had one such example in your code:
auto func_rref_const() const&& { ... } // ???
Right, that's similar to an ordinary parameter like
auto func_rref_const( X const&& x ) { ... } // ???
and in both cases the combination doesn't make sense: const
means you can't modify the argument, but &&
means it only accepts rvalues, and the only reason to accept only rvalues is to move from the argument... which is typically a modifying operation (except for types like int
).
In Cpp1 this comes up (and we have to teach why it doesn't make sense) because the low-level "how to pass a parameter" mechanical detail-oriented approach allows all the combinations, not only the ones that make sense. This is something that naturally disappears when you have a high-level "what I will use the parameter for" declarative usage approach, which also eliminates most need to overload them.
Cpp1 has a few of these things that I aim to eliminate... const&&
parameters is one, and protected
inheritance is another, where the constructs are legal but people haven't been able to find realistic uses for them. (I spent a long time in the 1990s trying to come up with a use case for protected inheritance, and one time I thought I found one but the next day I had to admit it was really contrived and gave it up.) My current plan for Cpp2 is to allow only public inheritance, and spell it as is
instead of public
... bring on the pitchforks and lederhosen! ... but if there's only IS-A public inheritance then I can stop teaching (a) why not to use private inheritance, (b) why protected inheritance has no uses, and (c) why public inheritance should always model IS-A (if Cpp2 spells it is
, it'll be right there in the keyword and not need a book to remind people that's what it should model).
And
out this
on a non-operator=
function is disabled because the only way for it to be useful would be to allow calling the function on an uninitialized object. I may allow that extension, but it seems a bit novel and I want to see actual motivating use cases down the road before spending time allowing and teaching it.
So, are we not getting named contructors? 😕
Edit: Referring to the named contructors as in parameter passing paper.
I like that construction and assignment will be unified via the = operator, can you just use the factory pattern for "named" constructors?
On 9 March 2023 19:14:03 Abhinav00 @.***> wrote:
And out this on a non-operator= function is disabled because the only way for it to be useful would be to allow calling the function on an uninitialized object. I may allow that extension, but it seems a bit novel and I want to see actual motivating use cases down the road before spending time allowing and teaching it.
So, are we not getting named contructors? 😕
— Reply to this email directly, view it on GitHubhttps://github.com/hsutter/cppfront/issues/266#issuecomment-1462631281, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AALUZQLDOSJNGR6L7SA3V23W3ITXRANCNFSM6AAAAAAVSHRC2U. You are receiving this because you are subscribed to this thread.Message ID: @.***>
So, are we not getting named contructors? 😕
Good news: In cppfront today you effectively already have named constructors throughout the language 😁 because any function with an out
parameter is a delegating constructor(*) when you pass an uninitialized argument to that parameter. For example, you can invoke f(out x: string)
with the call f(out mystring)
where mystring
is an uninitialized variable, and f
really will construct mystring
. I explain this more in this 1-min clip
The main difference I mean above is that if I were to allow a non-operator=
member function to have an out
this
parameter specifically, say a member function f(out this)
, then I could (but don't yet) allow out
also on the front of the function call, i.e., a syntax like out
myobject.f()
. Because a member function has access to the class's private data, such a member function could potentially also choose how to directly construct the individual members of the class (whereas other out
parameters can construct the object but only using the provided constructors). Other than that granularity, though, cppfront already allows any function with an out
parameter to act as a delegating constructor.(*)
(*) I say "delegating constructor" a couple of times there just to emphasize that already a Cpp2 function with an out
parameter is a constructor, but one that delegates to one of the class-provided constructors (it doesn't have access to the private members to fully customize construction to arbitrary states, which is a Good Thing because that would break encapsulation and the ability for a type to control its own invariant). That makes out
parameters be equivalent to granting the ability to write named constructors that delegate to other class-provided constructors, which is essentially the C++11 delegating constructor feature%0A%7D%3B) generalized throughout the language to all functions.
Thanks for clarifying!! One other question I had was under which parameter passing category would something like std::vector::reserve fall? And also, can an issue be opened to discuss callsite marking of function parameter to discuss different alternatives?
Sure thing, quick ack:
which parameter passing category would something like std::vector::reserve fall?
That's a non-const function, so it would be reserve: (this, new_capacity: size_type)
.
can an issue be opened to discuss callsite marking of function parameter to discuss different alternatives?
This has come up in a couple of issue, see #198 for example which summarizes the state of this for the time being. There's also some discussion in the d0708 paper.
That's a non-const function, so it would be reserve: (this, new_capacity: size_type)
Why is this
parameter not inout
? And even if it was inout
, reserve invalidates the iterators and any references, so I guess it should be move
? I'm not sure. And it shouldn't be in
because the vector may be unitialised and definitely not out
as it does not initialise any unitialised vector passed to it.
This has come up in a couple of issue,
Yes, that's why maybe one issue where we can discuss would be better instead of same thing scattered across various (closed) issues. I'll also mention all the issues it has come up in if I open a new issue.
Describe the bug All member functions are constant even if they change the class state (e.g. member variables).
To Reproduce This is the sample CPP2 code:
I've used Compiler Explorer.
It should generate the following C++ code:
But it generates the following C++ code:
Additional context function
mutableCall
shouldn't beconst
because it changes the class state (e.g. member variables).