I am attempting to move around a pointer by reference (T*&) between some template functions. Under certain conditions this pointer reference may get passed to a different function that accepts a void pointer reference (void*&). When I attempt to pass the templated type into the function accepting a void*&, it gives me the error:
error: cannot bind non-const lvalue reference of type 'void*&' to an rvalue of type 'void*'
This error is pretty self explanatory on its own. However I can't readily make sense of the error in context of the code. Here is a minimal reproduction of my error I was able to make in Godbolt (x86_64 gcc 10.2):
#include <iostream>
#include <type_traits>
void NonTempFunct(void*& Ptr)
{
std::cout << "Pointer Value: " << Ptr << ".\n";
}
template<typename T, typename = std::enable_if_t< std::is_pointer_v<T> >>
void TempFunct(T& Param)
{
std::cout << "Pointer found.\n";
NonTempFunct( Param );
}
template<typename T, typename = std::enable_if_t< !std::is_pointer_v<T> >, typename = void>
void TempFunct(T& Param)
{
std::cout << "Non pointer found. No op.\n";
}
int main()
{
int Value = 50;
int* pValue = &Value;
TempFunct( pValue );
return 0;
}
The error specifically complains about the invocation of NonTempFunct(void*&). As far as I am aware, there are no rvalues in this chain. They all have names and refer back to an automatically allocated variable.
I didn't stop here though, and fiddled with the code a bit. Using std::forward (NonTempFunct( std::forward<T&>(Param) );
) or std::move (NonTempFunct( std::move(Param) );
) when invoking NonTempFunct didn't change the error produced.
VERY curiously, when I switched the references in both TempFunct declarations to a universal reference (&&) the program did compile, however the wrong version was selected with SFINAE, suggesting the std::is_pointer_v<T>
check failed with universal references.
The one thing that did work was a reinterpret_cast in the call to NonTempFunct (without universal references).
NonTempFunct( reinterpret_cast<void*&>(Param) );
That compiles. I fear I don't understand C well enough to make sense of these results. My specific questions are:
- Where is the rvalue from the initial error coming from?
- Why does the use of a universal reference cause std::is_pointer_v to fail?
- Why does a reinterpret_cast bypass these issues?
CodePudding user response:
Case 1
Here we discuss the reason for the mentioned error.
The problem is that param
is an lvalue of type int*
and it can be converted to a prvalue of type void*
when passing it as the call argument in NonTempFunct( Param );
but the parameter of NonTempFunct
is a non-const lvalue reference which cannot be bound to an rvalue.
Essentially, the result of the conversion(int*
->void*
) will be a prvalue and a non-const lvalue reference cannot be bound to that rvalue.
To solve this you can either make the parameter of NonTempFunct
to be a const lvalue reference or simply a void*
as shown below
Method 1
//----------------------vvvvv---------->added this
void NonTempFunct(void *const& Ptr)
{
std::cout << "Pointer Value: " << Ptr << ".\n";
}
Method 2
//----------------vvvvv---------->removed the reference
void NonTempFunct(void* Ptr)
{
std::cout << "Pointer Value: " << Ptr << ".\n";
}
Case 2
Here we discuss the reason when we use universal reference, the program compiles without any error.
When you make the function template's parameter to be T&&
and use the call TempFunct( pValue )
then T
is deduced to be int*&
i.e., non const lvalue reference to a non const pointer to int
.
This means that std::is_pointer_v<T>
will be the same as std::is_pointer_v<int*&>
which will be false. Demo.
This in turn means that the first overloaded version will be SFINAE'd OUT. And since the second version is viable(as it uses !std::is_pointer_v<T>
which is the same as !std::is_pointer_v<int*&>
and so is true ), it will be used and we will get the output Non pointer found. No op.