2
#include <iostream>
#include <typeinfo>

class A {};

A f() { return A(); }

int main() {
    auto &&a = f();
    std::cout << typeid(f()).name() << std::endl;
    std::cout << typeid(a).name() << std::endl;
    std::cout << typeid(A{}).name() << std::endl;
    return 0;
}

It outputs

1A
1A
1A

Questions are,

  1. what does 1A mean here? (GCC on a linux box)
  2. auto && should be a forwarding reference, and in this case since f() returns an A object by value, a should be deduced to rvalue reference, correct?
  3. If a is indeed rvalue reference, and since a (temporary) rvalue's lifetime could only be extended by a const lvalue ref, a should go out of scope right after this line, is that correct?

Edit: Fixed the most vexing problem in the last cout statement, as many pointed out.

Summary of the great answers, for posterity:

  1. 1A is internal representation of type A, by Guillaume Racicot. And F1AvE indicates a Function style cast in typeid(A()) resulting in a prvalue, by Ted Lyngmo.
  2. turns out typeid() is not the tool to check reference, as it gives the referenced end type. Ted Lyngmo gave the right way to do it, and yes it is an r-value reference.
static_assert(std::is_rvalue_reference_v<decltype(a)>); // like this, ...
static_assert(std::is_same_v<decltype(a), A&&>);        // or like this
  1. Richard Critten gave the link that says "The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11)...".
QnA
  • 1,035
  • 10
  • 25
  • Just add ctor/dtor pair that prints something so you can verify the lifetime. – Aykhan Hagverdili May 27 '21 at 20:14
  • `1A` is an `A` type, and `F1AvE` is an `A()` constructor. – Eljay May 27 '21 at 20:17
  • 3
    _"...The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an __rvalue reference (since C++11)__, see reference initialization for details...."_ https://en.cppreference.com/w/cpp/language/lifetime So the lifetime of the temporary returned from `f()` is extended to be the same as the life time of `a`. – Richard Critten May 27 '21 at 20:17
  • 2
    @Eljay `A()` is declaration of function which takes no argument and return A. – rafix07 May 27 '21 at 20:18
  • @rafix • I was speaking of `F1AvE`. – Eljay May 27 '21 at 20:19
  • 1
    @Eljay Me too, check `std::cout << typeid(A{}).name() << std::endl;` what is printed ? A() doesnt mean ctor in this context. – rafix07 May 27 '21 at 20:20
  • Related: https://stackoverflow.com/questions/13230480/what-does-auto-tell-us – Eljay May 27 '21 at 20:21
  • 1
    @rafix • ahh, I think `F1AvE` is the result of the *most vexing parse*. – Eljay May 27 '21 at 20:23
  • Try piping the output to `c++filt` to demangle the typeid names. – Chris Dodd May 27 '21 at 20:35
  • 2
    Please don't change your question after an answer is posted. This is confusing to future readers. – HolyBlackCat May 27 '21 at 21:09
  • A reference to an rvalue is an lvalue. The type of a is A&& (auto deduces to A), and a is an lvalue. I think you confuse rvalue with rvalue reference. – mfnx May 27 '21 at 21:13
  • @Richard Critten, this is quite helpful, thanks! – QnA May 27 '21 at 21:26
  • 1
    @mfnx A bit of nitpicking. You're talking about two different `a`s here. The one that is an expression is an lvalue, but it's type is `A`, [not `A &&`](http://eel.is/c++draft/expr#type-1). The other one is a variable of type `A &&`, but it's not an lvalue per se, because only expressions have value categories. – HolyBlackCat May 27 '21 at 21:35
  • Oups ... I just noticed that the question has changed while I wrote and tried to improve on my answer. The thing I found most interesting (the `typeid(A())`) is now gone? Hmm, I noticed that there were updates but didn't refresh the screen because I didn't expect _that_ much to change. – Ted Lyngmo May 27 '21 at 21:55
  • 1
    @TedLyngmo yes.. what I wanted to ask originally was not actually about that part, but nonetheless learned a lot from your answer, thanks! – QnA May 27 '21 at 22:03
  • @HolyBlackCat just saw this, ok.. – QnA May 27 '21 at 22:06
  • @rafix07 • I realized that `typeid(A())` is much like `std::function`, thank you for helping me. – Eljay May 29 '21 at 00:30

2 Answers2

3

what does 1A mean here? (GCC on a linux box)

It means that the length of the name is 1 followed by the letter. There are many rules on how to mangle a type, but it encodes its namespace, template parameter value and other things.

auto && should be a forwarding reference, and in this case since f() returns an A object by value, a should be deduced to rvalue reference, correct?

The typeid operator discards all top level cv and ref qualifiers.

Also, it see through references and returns the type info for the refered type instead.

From the typeid operator page:

If type is a reference type, the result refers to a std::type_info object representing the referenced type.

But yes, the type deduced is A, so you are declaring A&&. But even if a is an rvalue reference, the expression a is an lvalue.

If a is indeed rvalue reference, and since a (temporary) rvalue's lifetime could only be extended by a const lvalue ref, a should go out of scope right after this line, is that correct?

The lifetime end at the end of the scope, per the lifetime extension rules.


Now for why the typeid is different.

This is not about rvalue or forwarding references. This is the most vexing parse problem. A() is a function that returns A and has no parameters.

Use {} for initialization and you'll see the problem disappear:

#include <iostream>
#include <typeinfo>

class A {};

A f() { return A(); }

int main() {
    auto &&a = f();
    std::cout << typeid(f()).name() << std::endl; // 1A
    std::cout << typeid(a).name() << std::endl;   // 1A
    std::cout << typeid(A{}).name() << std::endl; // 1A
    return 0;
}
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • @Racicot thanks, didn't expect that but just learned something new! I'll fix that, but the question was not originated from the fact that the third line prints differently. I think you are a few steps ahead of me. On a side note, `typeid(A())` hits the 'most vexing problem', but `return A()` does not, why? – QnA May 27 '21 at 20:59
  • @QnA because you cannot declare types in a return expression, but `typeid` works with both. You can desambiguate it with an extra pair of parens, that will force it to evaluate to an expression instead: `(A())` – Guillaume Racicot May 27 '21 at 21:10
1
  1. lA is the internal name for an A and "If type is a reference type, the result refers to a std::type_info object representing the referenced type." - and a is a reference type.
    F1AvE indicates a Function style cast in typeid(A()) resulting in a prvalue. You can compare with something more familiar, like int(), where the typeid::name() is changed to i from lA:

    std::cout << typeid(int()).name() << std::endl;  // FivE
    std::cout << typeid(A()).name() << std::endl;    // F1AvE 
    
    std::cout << typeid(int{}).name() << std::endl;  // i
    std::cout << typeid(A{}).name() << std::endl;    // 1A
    

    Explicit cast new_type ( ):

    If new_type names a non-array complete object type, this expression is an prvalue of type new_type, designating a temporary (until C++17) / whose result object is (possibly with added cv-qualifiers) (since C++17) of that type. If new_type is an object type, the object is value-initialized.

  2. Yes, as seen here:

    static_assert(std::is_rvalue_reference_v<decltype(a)>); // like this, ...
    static_assert(std::is_same_v<decltype(a), A&&>);        // or like this
    
  3. Its lifetime will not end at the end of the full expression but at the end of the enclosing block.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108