Why is the Visual Studio compiler happy with
void fn(int *&i)
{
;
}
and
void fn(IUnknown *const &p)
{
;
}
but not
void fn(IUnkown *&p)
{
;
}
where calling it looks like
IDXGIFactory *df = nullptr;
// init df
fn(df);
compiler error is
3 IntelliSense: a reference of type "IUnknown *&" (not const-qualified) cannot be initialized with a value of type "IDXGIFactory *" c:\Users\Carl\Documents\Visual Studio 2013\Projects\Project1\Project5\main.cpp 29 10 Project5
The closest thing I've dug up with research is that the compiler will only do one type conversion at a time, but that can't be right because then the const & version should break from doing a type and const conversion; however it is the & version that actually won't compile.
A non-const lvalue reference (like IUnknown*&) can only bind to an lvalue; it cannot bind to an rvalue. A const-qualified lvalue reference (like IUnknown* const&) can bind to an rvalue.
It's easier to consider a simpler case that does not involve pointers or function calls:
int i = 0;
double x = i; // (1) Well-formed
double const& y = i; // (2) Well-formed
double& z = i; // (3) Ill-formed
Here, i is an object of type int. When i is used in an expression, it is an lvalue.
In (1), we initialize the object x (of type double) from i (of type int). The type does not match, but this is okay, because there is an implicit conversion from int to double. The "result" of this conversion is an rvalue expression(*) of type double, which is used to initialize x.
In (2), we initialize the const-qualified reference y (of type double const&) from i. Again, the types do not match, so the implicit conversion is used to convert the int to double. The "result" of this conversion is an rvalue. As noted at the beginning, a const-qualified reference can bind to an rvalue, so y is bound to the "result" of the conversion.
In (3), we attempt to initialize the non-const reference z (of type double&) from i. The types do not match, so a conversion would be required. The conversion cannot be used here, because the "result" of the conversion is an rvalue, and as noted at the beginning, a non-const reference cannot bind to an rvalue.
C++ has special rules to permit const lvalue references to bind to rvalue expressions. You can find out why from other questions here on StackOverflow, like "How come a non-const reference cannot bind to a temporary object?"
Your case is exactly the same as this one: the type of your argument (IDXGIFactory*) is not the same as the type of the parameter (IUnknown* or a reference thereto), so an implicit conversion is required to convert the argument to the parameter type (in this case, it's a conversion from a pointer to a derived class to a pointer to a base class). The "result" of this conversion is an rvalue expression, however, so it cannot bind to the non-const reference IUnknown*&.
(*)It's really a prvalue; I've used the C++98 expression taxonomy in this answer for simplicity. See this question for information about the C++11 value categories.
Related
I have a bit confusion about this code:
struct A
{
A& bar()&&;
};
A& A::bar()&&
{
std::cout << "A::bar()&&\n";
return *this;
}
int main()
{
A{}.bar();// called by an rvalue
}
So what I understand is that bar can be called only by a modifiable-rvalue. Until this it is OK. But how can bar return a non-constant lvalue reference to that rvalue?
How bar() binds and returns a modifiable lvalue reference to that rvalue object?
The reason is that the this pointer for a class C can be either C* or const C* - not C& * or C&& * (those aren't actual types; you can't declare a C& * ptr). So, even when your method runs for an rvalue instance of class A, you get one of those two (GodBolt). And when you apply the * operator, you get an lvalue, not an rvalue.
This has to do with [expr.unary.op]/1
The unary * operator performs indirection: the expression to which it is applied shall be a pointer to an object type, or a pointer to a function type and the result is an lvalue referring to the object or function to which the expression points. If the type of the expression is “pointer to T”, the type of the result is “T”. [ Note: Indirection through a pointer to an incomplete type (other than cv void) is valid. The lvalue thus obtained can be used in limited ways (to initialize a reference, for example); this lvalue must not be converted to a prvalue, see [conv.lval]. — end note ]
emphasis mine
So when you dereference this yo get an lvalue. It doesn't matter if this is pointing to a temporary object or not, you will always get an lvalue. Since *this is an lvalue, you are legally allowed to return an lvalue reference, the program in syntactically correct. Semantically it is not, but that is a lot harder to test for and is often not something that is diagnosed as it requires quite a bit of static analysis.
It would be cool if the language could be updated where * only yields an lvalue when applied to this in a non-rvalue qualified function.
I can't do this:
int &&q = 7;
int &&r = q;
//Error Message:
//cannot convert from 'int' to 'int &&'
//You cannot bind an lvalue to an rvalue reference
If I understand correctly, when initializing an rvalue reference, there's a temporary variable got initialized too. So int &&q = 7; can be considered as:
int temp = 7;
int &&q = temp;
And when using a reference on the right side, I am actually using the referee. So int &&r = q; can be considered as:
int &&r = temp; //bind an lvalue to an rvalue reference, cause error, understandable
So above is how I understand the compiler error occurs.
Why adding std::forward can solve that?
int &&q = 7;
int &&r = std::forward<int>(q);
I know the std::forward always returns an rvalue reference, how is the reference returned by std::forward different from int&&q?
how is the reference returned by std::forward different from int&&q ?
Their value categories are different. And note that types and value categories are different things.
q is a named variable, it's qualified as lvalue, so it can't be bound to rvalue reference.
(emphasis mine)
the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;
While rvalue reference returned from function is qualified as xvalue, which belongs to rvalue.
a function call or an overloaded operator expression, whose return type is rvalue reference to object, such as std::move(x);
The difference between the expressions q and std::forward<int>(q) is that the former is an lvalue, while the latter is an rvalue (of fundamental category xvalue).
I've addressed similar concerns in this answer: the point is that q as an expression is an lvalue, because it has a name. std::forward<int>(q) (or the equivalent std::move(q)) are expressions which don't have names, and since they return (unnamed) rvalue references, they are xvalues, which is a subcategory of rvalue and can thus bind to an rvalue reference.
There're lots of rvalue related quiestion but I didn't found answers to these exact questions.
I can't wrap my mind around "named reference is a lvalue reference" rule of thumb.
It seems really strange -- we declare the reference as an rvalue but since we have to somehow use this reference (otherwise, what's the point?), we name it and since it's named it's an lvalue after all!
Consider this code:
int&& foo(int&& arg)
{
arg = 10;
return arg; // ERROR, `arg` is an lvalue since it's named!
}
foo(1);
The questions are:
When exactly does arg become an lvalue?
What would be the type of arg if the function was void and b) and c) lines were not present?
Lot's of articles (just citing first found result) say that there may be implicit lvalue to rvalue conversion but the opposite direction is not possible -- why? This example shows that arg -s converted from int&& to int& and then tries to implicitly convert int& to int&& which causes compilation error -- just the opposite behaviour! That's why we need std::move which is basicaly an explicit static_cast to rvalue type.
arg the variable has type int&& and no value category.
arg the expression (it is an expression at lines 3 and 4) has type int and value category "lvalue"
Lvalue to rvalue conversion changes the value category of an expression, without changing its type. If you write arg+1 inside the function, the lvalue expression arg of type int would undergo this conversion to produce a prvalue expression of type int, since that's what built-in + requires.
There are no "lvalue to rvalue" or reverse conversions between int& and int&& because expressions never have reference types. The error in your program is a failure to bind an rvalue reference (of type int&&) to an lvalue expression (of type int).
I watched Scott Meyers's extremely informative video on Universal References, in which I learned most of what I know about Rvalue references, moving, and forwarding. At one point he was talking about rvalueness as opposed to the type of a variable, and he said something to the effect of "rvalueness is independent of type".
I understand that you can have a method like this:
void func(MyType&& rRef)
{
// Do domething with rRef...
}
and that here rRef is an lvalue because it can be identified, its address can be taken, etc., even though its type is MyType&&.
But an rvalue cannot be any type, can it? I mean, it can only be a MyType&&, right? In that sense I thought type is not entirely independent of rvalueness. Maybe I'm missing something.
Updated: My point can be made clearer like this. If in func() I call one of two overloaded functions defined as
void gunc(MyType&& rRef)
{
// ...
}
void gunc(MyType& lRef)
{
// ...
}
i.e. either by calling gunc(std::move(rRef)) or gunc(rRef), it seems that the type of the resulting expression between parenthesis is not independent of rvalueness.
The type of an expression does not have any traces of references. So if for a moment we assume that references could have reference type, then we would have the following
int a = 0;
int &ra = a;
int c = a + 42;
int d = ra + 42;
In the above, the expression a would have type int, and the expression ra would have type int&. I think that in nearly all the rules of the spec that relate expressions to type, for example rules that say "expression E must be of type X", we would have to add "... or reference to type X" (think about cast operators). So my educated guess is that this would be too much of a burden to be useful.
C++ has the following types
static type of an expression
Just called "type of the expression" (if not otherwise specified that the dynamic one is meant). This is a property of expressions that designate the type of expressions abstracted away of what the expression refers to at compile time. For example if a refers to an int& or int variable, or is a literal 0, all those expression have type int.
dynamic type of an lvalue expression
This is the type that the non-base-class object that an lvalue expression refers to has.
ofstream fs("/log");
ostream &os = fs;
In this, os has the static type ostream and the dynamic type ofstream.
type of an object or reference
This is the type that an object or reference actually has. An object always has a single type and its type never changes. But what object exists at what location is something only known at runtime, so generally, "type of an object" is a runtime thing too
ostream *os;
if(file)
os = new ofstream("/log");
else
os = new ostringstream;
The type of the object denoted by *os (and the dynamic type of the lvalue *os aswell) is known only at runtime
int *p = new int[rand() % 5 + 1];
Here, the type of the array that was created by operator new is only known at runtime too, and (thanksfully) does and can not escape to the static C++ type system. The infamous aliasing rule (that forbids reading objects from incompatible lvalues, roughly speaking) speaks of "dynamic type" of objects, presumably because it wants to highlight that runtime concerns are of interests. But strictly speaking, saying "dynamic type" of an object is weird, because an object doesn't have a "static type".
declared type of a variable or member
This is the type that you gave in a declaration. In relation to the type of an object or the type of an expression, this sometimes can be subtly different
struct A {
A() { }
int a;
};
const A *a = new const A;
volatile const A *va = a;
Here, the expression a->a has type const int, but the declared type of the member that a->a resolves to has type int (the member entity). The type of the object denoted by a->a has type const int, because we created a const A object with the new expression, and therefore all non-static data members are implicitly const subobjects. In va->a, the expression has type volatile const int a, and the declared type of the variable still has type int and the type of the object referred to still has type const int.
When you say "type of a" and you declared "a" as "int &a;" you therefor always have to say what you mean by "type of a". Do you mean the expression? Then "a" has type int. It can even become nastier. Imagine "int a[10];". Here the expression "a" has type int* or int[10] depending on whether you consider the array to pointer conversion to have taken place in your expression or not, when you ask for the "type of a". If you ask for the type of the variable referred to by "a", then the answer uniquely is int and int[10] respectively.
So what type can an rvalue be of? Rvalues are expressions.
int &&x = 0;
int y = std::move(x);
int z = x;
Here, we have rvalues 0, and std::move(x). Both rvalues have type int. The expression x appearing in the initializer for z is an lvalue, even though it refers to the same object that the rvalue std::move(x) refers to.
Your last point in your question about the overloaded function called with an rvalue or lvalue respectively is interesting. Just because rvalue references are written as int && does not mean that rvalues have type int. They are called rvalue references because you can initialize them with rvalues and the language prefers that initialization over initializing an lvalue reference with an rvalue.
Also, it may be useful to see expressions in name-form that are rvalues
enum A { X };
template<int Y> struct B { };
If you use X or Y, they are rvalues. But those cases are the only one that I can think of.
I think you are leaving off part of his quote:
A final point is worth bearing in mind: the lvalueness or rvalueness
of an expression is independent of its type.
He explains it here. His main point was this:
The type of an expression does not tell you whether it is an lvalue or
an rvalue.
And in his concluding remarks:
In a type declaration, “&&” indicates either an rvalue reference or a
universal reference – a reference that may resolve to either an lvalue
reference or an rvalue reference. Universal references always have the
form T&& for some deduced type T.
Reference collapsing is the mechanism that leads to universal
references (which are really just rvalue references in situations
where reference-collapsing takes place) sometimes resolving to lvalue
references and sometimes to rvalue references. It occurs in specified
contexts where references to references may arise during compilation.
Those contexts are template type deduction, auto type deduction,
typedef formation and use, and decltype expressions.
The type T is used here to mean any type. It could be int&&, or double&&, or Widget&& - it doesn't matter.
First, let's limit the discussion to plain rvalue references and leave universal references aside. We're not talking about template <typename T> ... T &&var ...
As far as regular Type &&var = somevalue; case of rvalue reference, I think the meaning of it is this:
Whatever is bound to this reference was "disposable" when it was bound to the reference. It was going out of scope. If you modified it at the moment you bound it, no one would ever know. There were no other references to it at the time it was bound.
This allows us to take some liberties with rvalue references that we cannot take with other kinds of variables. The first use that comes to mind is to steal its contents with swap().
A function type (lvalue) can be converted to a pointer to function (rvalue).
int func();
int (*func_ptr)() = func;
But from (4.1/1)
An lvalue (3.10) of a non-function, non-array type T can be converted
to an rvalue.
Does it mean that a lvalue to rvalue conversion is not done on functions? Also, when an array decays to pointer doesn't it return a rvalue which is a pointer?
Functions are lvalues. A pointer to a function (a data type) can be
either; if you give it a name, it's an lvalue; otherwise, it's not
(roughly speaking). Pointers to functions obey all of the usual lvalue
to rvalue conversion rules. For simple types like the basic types or
pointers, the lvalue to rvalue conversion basically means reading the
variable.
void func(); // Declares func
(*(&func))(); // The expression &func is an rvalue
void (*pf)() = &func; // pf is an lvalue
(*pf)(); // In the expression *pf, pf undergoes an
// lvalue to rvalue conversion
Note that there is an implicit conversion of function to pointer to
function, and that the () operator works on both functions and
pointers to functions, so the last two lines could be written:
void (*pf)() = func;
pf();
As always, the result of the conversion is an rvalue (unless the
conversion is to a reference type). This is also the case when an array
is implicitly converted to a pointer; both arrays and functions can only
exist as lvalues, but they both implicitly convert to a pointer,
which is an rvalue. But that pointer can be used to initialize a
variable of the appropriate pointer type; such variables are lvalues.