const T a {}; // constant of type T
const T& b {}; // ???
T c {}; // variable of type T
T& d {}; // error
What is the difference between a and b?
b is a reference but I don't assign a object to it: in that instruction I initialize it by T constructor.
The address of b is between the addresses of a and c, so it seems the b and a have no differences.
And if I can declare and initialize b why d gives compilation error?
I talked about a generic type T. I tested the code above either for primitive types and classes and the results are the same.
In these declarations
const T a {}; // constant of type T
const T& b {};
there is created constant object a that is default initialized and constant reference b to temporary object that is default initialized.
The compiler issues an error for this declaration of a reference
T& d {}; // error
because there is declared a non-constant reference to a temporary object.
You could declare an rvalue reference the following way
T&& d {};
Here is a demonstrative program
#include <iostream>
int main()
{
const int &ri {};
std::cout << "ri = " << ri << '\n';
int && rri {};
std::cout << "rri = " << rri << '\n';
return 0;
}
The program output is
ri = 0
rri = 0
Related
I have two classes, with one on them having an object of the other as reference Member:
class myClass{
public:
myClass() { };
};
class mySecondClass {
public:
mySecondClass(myClass& reference)
{
myClassReference = reference; //doesn't get accepted by the compiler
};
myClass& myObjectReference;
};
I found out (thanks to Passing by reference to a constructor
), that mySecondClass(myClass& reference) : myObjectReference(reference){}; does the job.
But why can't I use myObjectReference = reference;?
It's because { myClassReference = reference; } is considered as an
assignment to something which is supposed to be already initialised.
The member initialisation list : member1{value1}, member2{value2}...
is intended to provide the initial value (nothing is supposed to
be valid before).
For the specific case of a reference, it's the same situation as
int i=4;
int &r=i; // the reference is initialised (alias for i)
r=5; // the reference itself is not changed but
// the referenced object (i) is assigned
In the constructor
mySecondClass(myClass& reference)
{
myClassReference = reference; //doesn't get accepted by the compiler
};
there is used reference myClassReference that shall be initialized when it is created. However the variable was not initialized. There is used the copy assignment operator.
In this constructor
mySecondClass(myClass& reference) : myObjectReference(reference){}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
the reference is created and initialized in the mem-initializer list.
To make it more clear consider the following demonstrative program.
#include <iostream>
struct Int
{
Int() { std::cout << "Int()\n"; }
Int( int x ) : x( x ) { std::cout << "Int( " << x << " )\n"; }
Int & operator =( int x )
{
std::cout << "Int & operator =( " << x << " )\n";
this->x = x;
return *this;
}
int x;
};
struct A
{
A( int x )
{
this->value = x;
}
Int value;
};
struct B
{
B( int x ) : value( x ) {}
Int value;
};
int main()
{
A a( 10 );
std::cout << '\n';
B b( 10 );
return 0;
}
Its output is
Int()
Int & operator =( 10 )
Int( 10 )
That is when the body of the constructor A gets the control the data member was already created using the default constructor of the class Int. Within the body of the constructor there is used the assignment operator.
Opposite to the constructor of the class A the constructor of the class B created the data member in the mem-inkitializer list using the constructor of the class Int with a parameter.
So now imagine that you are using a reference in the class A. Then it is "default-initialized" that is it is not actually initialized by any valid object to which it shell refer. So in the body of the constructor you are trying to assign a value to an invalid reference that refers nowhere. So the compiler issues an error.
consider something like this:
#include <iostream>
struct C {
C(double x=0, double y=0): x(x) , y(y) {
std::cout << "C ctor " << x << " " <<y << " " << "\n";
}
double x, y;
};
struct B {
B(double x=0, double y=0): x(x), y(y) {}
double x, y;
};
struct A {
B b[12];
A() {
b[2] = B(2.5, 14);
b[4] = B(56.32,11.99);
}
};
int main() {
const B& b = A().b[4];
C c(b.x, b.y);
}
when I compile with -O0 I'm getting the print
C ctor 56.32 11.99
but when I compile with -O2 I'm getting
C ctor 0 0
I know we can use const reference to prolong a local temporary, so something like
const A& a = A();
const B& b = a.b;
would be perfectly legal. but I'm struggling to find the reasoning for why the same mechanism/rule doesn't apply for any kind of temporary
EDIT FOR FUTURE REFERENCE:
I'm using gcc version 6.3.0
Your code should be well-formed, because for temporaries
(emphasis mine)
Whenever a reference is bound to a temporary or to a subobject thereof, the lifetime of the temporary is extended to match the lifetime of the reference
Given A().b[4], b[4] is the subobject of b and the data member b is the subobject of the temproray A(), whose lifetime should be extended.
LIVE on clang10 with -O2
LIVE on gcc10 with -O2
BTW: This seems to be a gcc's bug which has been fixed.
From the standard, [class.temporary]/6
The third context is when a reference is bound to a temporary object.36 The temporary object to which the reference is bound or the temporary object that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference if the glvalue to which the reference is bound was obtained through one of the following:
...
[ Example:
template<typename T> using id = T;
int i = 1;
int&& a = id<int[3]>{1, 2, 3}[i]; // temporary array has same lifetime as a
const int& b = static_cast<const int&>(0); // temporary int has same lifetime as b
int&& c = cond ? id<int[3]>{1, 2, 3}[i] : static_cast<int&&>(0);
// exactly one of the two temporaries is lifetime-extended
— end example ]
A().b[4] is not a temp or rvalue, which is why it does not work. Whilst A() is a temporary, you are creating a reference to an array element that exists at the point of creation. The dtor then triggers for A(), which means the later access to b.b becomes somewhat undefined behavior. You'd need to hold onto the A& to ensure b remains valid.
const A& a = A();
const B& b = a.b[4];
C c(b.x, b.y);
Here I declared two template classes: A and B, B derives from A:
template<typename T>
class A {
public:
int a;
T t;
};
template<typename T>
class B : public A<T> {
public:
int b;
};
And I make a shared_ptr<B<T>> and assign it to shared_ptr<A<T>>, it's ok:
auto b = std::make_shared<B<std::string>>();
std::shared_ptr<A<std::string>> a = b;
Here I declared a template function accept shared_ptr A<T>:
template<typename T>
void proc(std::shared_ptr<A<T>> &a) {
std::cout << a->a << std::endl;
}
it accepts a as argument, but rejects b:
proc<std::string>(a); // OK
proc<std::string>(b); // template argument deduction/substitution failed
// cannot convert 'b' (type 'std::shared_ptr<B<std::__cxx11::basic_string<char> > >') to type 'std::shared_ptr<A<std::__cxx11::basic_string<char> > >&'
I use g++ as compiler with -std=c++11.
This error brings me a lot problems and how could I fix this elegantly?
Given proc<std::string>(b);, b needs to be converted to std::shared_ptr<A<std::string>>. That means a temporary std::shared_ptr<A<std::string>> will be constructed and then passed to proc. The parameter type of proc is an lvalue-reference to non-const, i.e. std::shared_ptr<A<T>> &, which can't bind to temporaries.
You can change the parameter type to lvalue-reference to const, which could bind to temporaries. e.g.
template<typename T>
void proc(const std::shared_ptr<A<T>> &a) {
// ^^^^^
std::cout << a->a << std::endl;
}
First of all, you make a shared_ptr called:
auto b = std::make_shared<B<std::string>>();
Is of type std::shared_ptr<B<std::string>> and,
std::shared_ptr<A<std::string>> a = b;
Is of type std::shared_ptr<A<std::string>>...
In your function parameter, however, you have:
void proc(std::shared_ptr<A<T>> &a)
Which only points to the shared_ptr of A, not B, so it is obvious that B won't become A...
The solution would be to remove the lvalue reference of a from the function definition, like:
void proc(std::shared_ptr<A<T>> a)
so, it doesn't refer to A, and B can easily be converted to A during function call...
Edit: Added an explanation...
Explanation:
Remember pointers? from C... yes, they do the same function of references:
// Compilable both in C and C++...
int add(int a, int b, int * more_than_3) {
int const result = a + b;
if (result > 3)
*more_than_3 = 1;
return result;
}
Yeah, these which would the function of pseudo-return types in C. Like:
// Compilable both in C and C++...
int main(void) {
int more_3;
int const res = add(2, 3, &more_3);
printf("Sum of 2 + 3 is %i\n", res);
if (more_3)
printf("Given two numbers' sum is more than 3");
}
Here, an extra argument is passed which takes the address of a variable (References also do the same thing, they share their address with the variable with whom they are referenced...)
Remember, references and pointers store the address of another variable inside of them...
This might be the reason why they made the address of operator (&) also act for references in C++...
Also, unneeded, but the answer which was posted here by #songyuanyao worked, because:
void proc(std::shared_ptr<A<T>> const &a)
uses a constant reference, a reference to a constant expression, not a variable, so it didn't matter if they mismatched (A and B)
Here is the code:
namespace NS
{
class B
{
public:
template<typename T> friend B& operator<<(B& b, T const& obj);
};
template<typename T> B& operator<<(B& b, T const& obj)
{
// using b and obj
return b;
}
}
int main()
{
NS::B() << 2.71 << 123 << "abcdef"; // doesn't compile in gcc
NS::B b; b << 2.71 << 123 << "abcdef"; // OK
}
The first line in main compiles in VS2015 (and some earlier ones) and doesn't compile in gcc (I tried in 6.3, 4.3.2).
Which compiler does the right thing?
I thought the temporary object's (NS::B()) lifetime would be up to the end of the statement (;), so it's ok to pass it to my operator<< by non-const reference.
If it isn't so, could you tell me why?
Thanks.
The standard says that temporary objects may not be converted to non-const lvalue references implicitly. See [dcl.init.ref]/8.6.3.5.2 in n4628.
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be
const), or the reference shall be an rvalue reference. [ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const
— end example ]
To solve this:
template<class T>
T& as_lvalue(T&&t){return t;}
This function takes an rvalue t and returns an lvalue reference.
as_lvalue(NS::B()) << 2.71 << 123 << "abcdef";
The above is both standard compliant and safe.
So I'm implementing a native arrays wrapper which will allow such to be passed as function arguments and to be returned. I'm having a trouble however with casting it to a native array as native arrays can't be returned. As an replacement I decided to use 'rvalue' reference return type of the casting operator but this will not act correctly because if I want to bind the returned object into an 'rvalue' reference in order to extend it's life-time this won't happen as it's an 'xvalue' and not 'prvalue'. Is there any solution for the problem? Maybe some 'prvalue' cast? Or if there is some other way to implement this implicit cast to 'array'?
The class:
template<typename type>
struct tmp
{
tmp() {}
tmp(const tmp &) = default;
tmp(const type & arg) : tmp(*(const tmp*)arg) {}
&& operator type() && {return static_cast<type&&>(d);}
~tmp () { cout << "tmp destructor" << endl; }
type d;
};
And the code that uses it:
tmp<tmp<int [4]>> Func() // used 'tmp<int [4]>' instead of array to track object destruction (but normally it should be an native array type
{
return tmp<tmp<int [4]>>();
}
int main()
{
tmp<int [4]> &&tmp1 = Func(); //implicit cast (from 'tmp<tmp<int [4]>>') to 'tmp<int [4]>', calls tmp::operator type()
cout << "Here" << endl;
return 0;
}
The program output:
tmp destructor
tmp destructor
Here
As you see the return value of the cast operator is not extended.
Life example.
A prvalue is an rvalue that is not an xvalue, aka "a temporary object or subobject thereof, or a value that is not associated with an object."
You cannot create an array that is a a temporary object (12.2), nor can you create an array value that is not associated with an object.
For an array to be a prvalue, that leaves a subobject thereof of a temporary object.
So a tmp:
template<typename type>
struct tmp
{
tmp() {}
tmp(const tmp &) = default;
tmp(tmp &&) = default;
tmp(const tmp &&o):tmp(o) {}
tmp(tmp &o):tmp(const_cast<tmp const&>(o)){}
template<class... Ts>
tmp(Ts&&...ts) : v{std::forward<Ts>(ts)...} {}
~tmp () { std::cout << "tmp destructor\n"; }
type v;
};
A wrap_as_tmp:
template<class X, class... Ts>
tmp<X> wrap_as_tmp(Ts&&... ts)
{
return {std::forward<Ts>(ts)...};
}
to track destruction we use noisy:
struct noisy {
~noisy() { std::cout << "bang\n"; }
};
Then test:
int main() {
auto&& x = wrap_as_tmp<noisy[4]>().v;
std::cout << "There\n";
}
and note that There outputs before the noisy objects explode.
live example
Note the use of .v at the end of the function call.
If your goal is to avoid that, too bad, you cannot.