IUnknown pointer reference

2019-09-02 13:10发布

问题:

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.

回答1:

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.