2

I would like too know the rationale behind o.f and o->f being prvalues, where o is an object, and f is a non-static member function.


EDIT 1

My point is that putting it into a prvalue category seems to be tantamount to an ad hoc decision as even though it is not an lvalue in its fullest, it's not yet a complete rvalue also.

  1. g (a regular function) is not assignable, just like f (a non-static member function);
  2. an address of both can somewhat be taken -- though pointer to member function is not required to contain an address, they are sorta semantically close operations. EDIT -- THAT IS NOTED IN COMMENTS THAT IT IS NOT AN ADDRESS OF o.f OR o->f THAT IS TAKEN
  3. member function cannot init lvalue reference, but then it should init rvalue one, but can it?.

And that is odd -- consider point 1) above, only modifiable lvalues are assignable by definition, so lvalue properties are not violated by this "non-assignability". But in 3), all prvalues shall be able to init rvalue references, but a member function can not, presumably.


EDIT 2

I am particularly interested in something like a justification paper from committee people or something like this, showing why it is made this way.

I may be terribly wrong, but I may imagine a hypothetical set of rules that would allow o.f to follow all the behaviors of lvalue. We necessarily need a binding object only for the sake of function call, and technically there is still one definition of the member function in the programm. That seems to be a bit whimsical for me that we cannot trat pointer to non-static member functions exactly like regular function pointers except were call is attempted, where we should provide an argument for a implicit object parameter.

Enlico
  • 23,259
  • 6
  • 48
  • 102
Anton Tretyakov
  • 303
  • 1
  • 9
  • A prvalue (“pure” rvalue) is an rvalue that is not an xvalue. [ Example: The result of calling a function whose return type is not a reference is a prvalue. ] – foragerDev Sep 16 '22 at 17:44
  • I can't imagine code where `o.f` is valid. You can't get pointer-to-member-function this way and the only other use would be to actually call the function (or did you mean `o.f()`?) – Yksisarvinen Sep 16 '22 at 17:45
  • 1
    @Yksisarvinen `o.f` is still considered an expression with a value category by itself. But it is explicitly only allowed to be used as postfix expression in a function call expression, so I don't think its value category has any practical relevant at all. @Anton Why exactly do you care that it is a prvalue? Is this for any practical reason or just a question of why it was given a value category at all? – user17732522 Sep 16 '22 at 17:54
  • @Yksisarvinen nah, I meant exactly what I wrote, the member function itself, not a call to it. – Anton Tretyakov Sep 16 '22 at 17:58
  • I guess it's because `f` is not really a subobject of `o`, `o.f` (not really) computes something like `std::bind_front(f,o)` – apple apple Sep 16 '22 at 18:13
  • @AntonTretyakov the title of your question has nothing to do with the body of your question. Please [edit] your question to fix that. – Remy Lebeau Sep 16 '22 at 18:32
  • @user17732522 to be fairly honest, I'm revisiting cppreference page on value categories, and it just the thing I haven't really thought about before. I mean, `g` (a regular function) is not assignable, just like `f` (a non-static member function). An address of both can *somewhat* be taken -- though pointer to member function is not required to contain an address, it is sorta a semantically close operations. Yeah, member function cannot init lvalue reference, but then it should init rvalue one, but is there a syntax for this? – Anton Tretyakov Sep 16 '22 at 18:39
  • @user17732522 my point is that putting it into a `prvalue` category seems to be tantamount to an ad hog decision as even though it is not an `lvalue` in its fullest, it's not yet a complete `rvalue` also. I should probably add this into the question. – Anton Tretyakov Sep 16 '22 at 18:44
  • @RemyLebeau I guess so, thank you for pointing it out. – Anton Tretyakov Sep 16 '22 at 18:44
  • "_can somewhat be taken_": But not with this member access expression. The expression after `&` to take a pointer-to-member is a different kind of expression (an _id-expression_). The member access expression referring to a non-static member function can literally only be used immediately in a function call. A reference to a non-static member function can not be formed at all (neither lvalue reference nor rvalue reference, neither bound nor unbound to an object). – user17732522 Sep 16 '22 at 18:50
  • @user17732522yeah, good point. So, it is violating `lvalue` properties, but so far it is a `prvalue`, can it be used to init rvalue references, as all `prvalue`s do? – Anton Tretyakov Sep 16 '22 at 19:01
  • @AntonTretyakov It can be used for absolutely nothing but putting `(/*...*/)` after it to call it. As far as I can tell it was given a value category at all only so that all expressions consistently have one. I don't think anything would change if it was defined to be a glvalue instead. However a glvalue is supposed to refer to some object or function, which the member access expression doesn't really. That seems to me to be the only reason that prvalue was chosen. I could be missing something more subtle though. – user17732522 Sep 16 '22 at 19:25
  • @AntonTretyakov the title now make less sense, pointer to a member function can be a lvalue without problem (`auto p = &T::f`, `p` is a pointer to a member function) – apple apple Sep 16 '22 at 19:27
  • @appleapple rectified, thank you! – Anton Tretyakov Sep 16 '22 at 19:30
  • Finding documented committee reasoning for this choice is going to be tricky. It has been defined this way since C++98 (then "not lvalue" instead of "prvalue"). There isn't much public documentation of the standardization process for C++98. – user17732522 Sep 16 '22 at 19:31
  • 1
    Similar to my reasoning above in [CWG issue 2534](https://wg21.link/cwg2534) reference is made to this and the main argument for making a pseudo-destructor expression a prvalue is for consistency and because it doesn't identify an object or function. – user17732522 Sep 16 '22 at 19:34
  • 2
    Also [CWG issue 2458](https://wg21.link/cwg2458) discusses whether expressions naming bound/unbound non-static member functions should be lvalues or prvalues. In the end it seems to simply come down to how well it fits into the concept that lvalues determine identity of objects/functions and how it simplifies/complicates the wording of the rest of the standard. Also follow the links in the issue for more discussions on the topic. I don't think there is any practical relevance to this determination at all. – user17732522 Sep 16 '22 at 19:38
  • @user17732522 wow, thank you for the links provided! It's a bit soothing to know there is a person having a related question :) Anyways, even thought the question is of pure theoretical nature, I'm still learning useful thing, after all. – Anton Tretyakov Sep 16 '22 at 20:26
  • _c++ why non-static member function is a prvalue?_ The decision looks to be completely arbitrary, and complicates the «definition» (which is more like non-normative summary) of lvalue – Language Lawyer Sep 20 '22 at 12:07
  • circumstantial but current: https://wg21.link/cwg2534 rules that pseudo-dtor expr (the `n.~int32_t` subexpression of `n.~int32_t();`) is prvalue, not lvalue as it's been specified previously. Because it doesn't identify an object or a function. – Cubbi Sep 26 '22 at 22:06

1 Answers1

0

just my guess. In expression o.f(), f is not really a subobject of o, it effectively bind o and f together which can then be called.

which does something like

//------------------------------o.f------------------------------------()
[&](auto&&... args){return x.f(std::forward<decltype(args)>(args)...);}()

except it preserve overloads and (thus) cannot be used in other way (like store in variable).


note that "non-static member function" is pretty different from "pending member function call"

struct X{void f(){}};

void f(){
    auto mfp = &X::f; // you can definitely get member function pointer
    X x;

    x.f(); // x.f is pending member function call
    (x.f)(); // (x.f) is pending member function call
    (x.*mfp)(); // (x.*mfp) is pending member function call

    // [&](auto&&... args){...} is also a prvalue
    [&](auto&&... args){return x.f(std::forward<decltype(args)>(args)...);}();

    // std::bind_fount(&X::f,x) is also a prvalue
    std::bind_front(&X::f,x)(); // but you don't get overloads resolution here
}
apple apple
  • 10,292
  • 2
  • 16
  • 36
  • btw also `(o.*&decltype(x)::f)()`, where `(o.*&decltype(x)::f)` is also a prvalue (just as `o.f`) – apple apple Sep 16 '22 at 18:37
  • Yeah, we are providing an argument for an `implicit parameter`. But is there any justification for making it a `prvalue`? I mean, I could be terribly wrong, but I may imagine language rules for this situation that allow `o.f` to be a `lvalue` (aka to follow its properties) and still bind to the object provided. After all, we do not need the object itself neither to determine the type of a member, and its value nor to use it it *hypothetical* context other then calls. Should probably add it into the question. – Anton Tretyakov Sep 16 '22 at 19:17
  • I'm not sure why it should be a lvalue? the result of `std::bind_front(&decltype(o)::f,o)` is (most likely, a function object) `prvalue`. And for example, `int x;`, `x+1` is prvalue, too. so `o.f` is `prvalue` make much sense to me. – apple apple Sep 16 '22 at 19:30
  • @AntonTretyakov (forgot to @you) – apple apple Sep 16 '22 at 19:38
  • Yeah, but `x+1` creates a new object. `std::bind_front` will too, as I may understand. I extended the question a bit, so probably it conveys an idea better. What I mean is that a member function clearly does not have any duplicates in program and won't have ones on runtime. In addition, we need an object for a sole purpose of calling an binding to an implied parameter. In cannot see why member functions cannot be treated exactly like just functions in contexts other then calls? It seems for me that now they not just violate all `lvalue` properties, but also a bit a `prvalue` ones also. – Anton Tretyakov Sep 16 '22 at 19:39
  • @AntonTretyakov it's not one function though. `o` is dynamic, just like `x`, which may not even make sense to form `o.f` in compile time (like `x+1`) – apple apple Sep 16 '22 at 19:41
  • I mean, sure, there may be a dynamic dispatch for a polymorphic object, but there is, eventually, only one definition of member function per se, as well as there is only one definition of a regular function. Similarly, there may be a set of regular functions `g`, but each having only one definition. But, even following your logic -- if it is not one function, then it is polymorphic, and `prvalue` cannot be polymorphic. – Anton Tretyakov Sep 16 '22 at 20:14
  • @AntonTretyakov well my point is not there are multiple function, but bind dynamic object `o` info it. Even if there is only single function, there are multiple *binding result* which may or **may not** be able to determined without actual object. – apple apple Sep 16 '22 at 20:15
  • and I think a binding result is pretty good fit of prvalue. – apple apple Sep 16 '22 at 21:11
  • Gonna put all comments subsequently together :) 1) yes, but in case of a member function, we know precisely which one is called, the type is known exactly. Object is needed because this function has `this` implicit object, in fact, (`this` pointer may be adjusted to point to the correct subclass). Otherwise, it is technically the same as regular function. 2) I think we are actually trying to speak about different things here. I will put my point in another terms -- it just seems for me that regular func pointer are equally "dynamic", as member, so why difference in value? – Anton Tretyakov Sep 16 '22 at 22:05
  • Not enough space for all 3, so continue here: 3) I prefer to think that `1` here is not addressing an existing object, but created every time. The compiler may implement in differently, but semantics seems to be like this. With function pointers, there is a precise address of a place where functions starts, and we just use different names for this address. And, if looking from this perspective, it's even more weird for me that just function pointers and non-static member pointers differ so much -- not just in value category, but in general. – Anton Tretyakov Sep 16 '22 at 22:09
  • @AntonTretyakov well you can definitely get (non-static) member function pointer by `&T::f` or `&decltype(o)::f`, the pending call (`o.f` or `(o.*fp)`, `(o.*&decltype(o)::f)`) is a different thing. – apple apple Sep 17 '22 at 08:35
  • @AntonTretyakov fwiw, `auto fp = &decltype(o)::f` – apple apple Sep 17 '22 at 09:23