Why are C Strings always lvalue references? [duplicate] - c++

This question already has answers here:
Why are string literals l-value while all other literals are r-value?
(4 answers)
C++ specialized template function receiving literal string
(1 answer)
Closed 7 months ago.
When trying to write a function that behaves differently for lvalue references and rvalue references I noticed, that C strings seem to always be considered as lvalue references.
#include <iostream>
template <typename T>
struct Holder {
T value;
};
template <typename T>
Holder<T> some_function(T&& t) {
std::cout<< "overload T&&, "
<< typeid(T).name()
<< std::endl;
return {std::move(t)};
}
template <typename T>
Holder<T*> some_function(T& t) {
std::cout<<"overload T&, "
<< typeid(T).name()
<< std::endl;
return {&t};
}
Here, the returned Holder holds a pointer to the argument if some_function is passed an lvalue reference and it moves the argument otherwise.
When calling some_function on std::string it behaves as expected. But when I pass const char* strings to it, it always consideres the argument to be an lvalue-reference:
int main()
{
std::string x = "lvalue string";
auto holder_string_lvalue = some_function(x);
auto holder_string_rvalue = some_function(std::string("rvalue string"));
const char* y = "lvalue cstring";
auto holder_cstring_lvalue = some_function(y);
auto holder_cstring_rvalue = some_function("rvalue cstring");
return 0;
}
Output:
overload T&, NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
overload T&&, NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
overload T&, PKc
overload T&, A15_c
I don't understand the fourth line in this. Also, the code should now have a Holder with an invalid pointer holder_cstring_rvalue, does it not?
How would I make sure Holder never points to a temporary object for arbitrary T, including c strings?
Compiler explorer link: https://godbolt.org/z/EMfETYc7e

Related

Why does the rvalue parameter change to lvalue when used? [duplicate]

This question already has answers here:
Why is function parameter always an lvalue?
(1 answer)
C++11: Why rvalue reference parameter implicitly converted to lvalue
(2 answers)
r-value parameters in a function
(3 answers)
Is the r-value parameter really an l-value parameter inside the function scope?
(1 answer)
Closed 4 months ago.
I pass rvalue std::move(x) to testForward(T&& v), but it calls print(T& t) inside.
It seems that the rvalue v has changed to an lvalue before it calls print(). I do not know why this happened. Can anyone explain it?
#include<iostream>
using namespace std;
template<typename T>
void print(T& t) {
std::cout << "Lvalue ref" << std::endl;
}
template<typename T>
void print(T&& t) {
std::cout << "Rvalue ref" << std::endl;
}
template<typename T>
void testForward(T&& v) {
print(v); // call print(T& t);
}
int main(int argc, char* argv[])
{
int x = 1;
testForward(std::move(x)); // output: Lvalue ref
}
The value category of the expression v is an lvalue, because:
... Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression
If you want to forward a forwarding reference as its original category, use std::forward, ie,
template<typename T>
void testForward(T&& v) {
print(std::forward<T>(v));
}

C++ concept member check type ambiquity with reference

I am learning C++ concepts, and I have an obnoxious problem:
I do not know how to differentiate between member variable being variable of type int and member variable being a int&.
Reason for this is that check I am using is using instance.member syntax and in C++ that returns a reference.
Full example:
#include <iostream>
#include <concepts>
template<typename T>
void print(T t) {
std::cout << "generic" << std::endl;
}
template<typename T>
requires requires(T t){
{t.val} -> std::same_as<int&>;
}
void print(T t) {
std::cout << "special" << std::endl;
}
struct S1{
int bla;
};
struct S2{
int val = 47;
};
int x = 47;
struct S3{
int& val=x;
};
int main()
{
print(4.7);
print(S1{});
print(S2{});
print(S3{});
}
I wish print(S3{}) would be handled by generic case, not the special one.
Note that changing my requires stuff to:
{t.val} -> std::same_as<int>;
makes S2 not match the template so that does not work(like I said I think that member access in C++ returns a reference).
Is there a way to fix this?
The problem here is that expression concept checks use decltype((e)) in the check rather than decltype(e) (the extra parentheses matter).
Because t.val is an lvalue of type int (expressions never have reference type), decltype((t.val)) is int& regardless, as you've discovered.
Instead, you need to explicitly use the single-paren syntax:
template <typename T>
requires requires (T t) {
requires std::same_as<decltype(t.val), int&>;
}
void print(T t) {
std::cout << "special" << std::endl;
}
Or
template <typename T>
requires std::same_as<decltype(T::val), int&>
The solution is:
template <typename T>
requires requires(T t)
{
requires std::is_same_v<decltype(t.val), int>; // or `int &` for references
}
void print(T t)
Clang has a bug that causes {T::val} -> std::same_as<int> to work as well, even though the type of the lhs is said to be determined as if by decltype((...)), which should return int & here.
Note that "member access in C++ returns a reference" is false. It can't be the case because expressions can't have reference types. When you write a function with a reference return type, calling it produces an lvalue of a non-reference type.
decltype will add reference-ness to the expression types depending on value category (& for lvalues, && for xvalues, nothing for prvalues). That's why people often think that expressions can have reference types.
It also has a special rule for variables (as opposed to general expressions), which causes it to return the variable type as written, disregarding the expression type & value category. And apparently t.val counts as a variable for this purpose.

Why doesn't my forward_ function work for rvalues?

I've understood how std::move works and implemented my own version for practice only. Now I'm trying to understand how std::forward works:
I've implemented this so far:
#include <iostream>
template <typename T>
T&& forward_(T&& x)
{
return static_cast<T&&>(x);
}
/*template <typename T>
T&& forward_(T& x)
{
return static_cast<T&&>(x);
}*/
void incr(int& i)
{
++i;
}
void incr2(int x)
{
++x;
}
void incr3(int&& x)
{
++x;
}
template <typename T, typename F>
void call(T&& a, F func)
{
func(forward_<T>(a));
}
int main()
{
int i = 10;
std::cout << i << '\n';
call(i, incr);
std::cout << i << '\n';
call(i, incr2);
std::cout << i << '\n';
call(0, incr3); // Error: cannot bind rvalue reference of type int&& to lvalue of type int.
std::cout << "\ndone!\n";
}
Why must I provide the overloaded forward(T&) version taking an lvalue reference? As I understand it a forwarding reference can yield an lvalue or an rvalue depending on the type of its argument. So passing the prvalue literal 0 to call along with the incr3 function that takes an rvalue reference of type int&& normally doesn't need forward<T>(T&)?!
If I un-comment the forward_(T&) version it works fine!?
I'm still confused about: why if I only use the forward_(T&) version does it work for any value category? Then what is the point in having the one taking a forwarding reference forward_(T&&)?
If I un-comment the version taking lvalue reference to T& and the one taking forwarding reference T&& then the code works fine and I've added some messages inside both to check which one called. the result is the the one with T&& never called!
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
I mean running the same code in the driver program I've shown above.
A T&& reference stops being a forwarding reference if you manually specify T (instead of letting the compiler deduce it). If the T is not an lvalue reference, then T&& is an rvalue reference and won't accept lvalues.
For example, if you do forward_<int>(...), then the parameter is an rvalue reference and ... can only be an rvalue.
But if you do forward_(...), then the parameter is a forwarding reference and ... can have any value category. (Calling it like this makes no sense though, since forward_(x) will have the same value category as x itself.)
It is clear that you wander why having two versions of std::forward; one takes an l-value reference to the type parameter T& and the other takes a universal reference (forwarding) to the type parameter. T&&.
In your case you are using forward_ from inside the function template call which has forwarding reference too. The problem is that even that function call called with an rvalue it always uses forward_ for an lvalue because there's no way that call can pass its arguments without an object (parameter). Remember that a name of an object is an lvlaue even if it's initialized from an r-value. That is why always in your example forward_(T&) is called.
Now you ask why there's second version taking forwarding reference?
It is so simple and as you may have already guessed: it is used for r-values (the values not the names of those objects).
Here is an example:
template <typename T>
T&& forward_(T& x)
{
std::cout << "forward_(T&)\n";
return static_cast<T&&>(x);
}
template <typename T>
T&& forward_(T&& x)
{
std::cout << "forward_(T&&)\n";
return static_cast<T&&>(x);
}
int main()
{
int i = 10;
forward_(i); // forward(T&) (1)
forward_(5); // forward(T&&) (2)
forward_("Hi"); // forward(T&) (3)
}

How does template argument deduction distinguish between an lvalue and a literal/compile-time value

This is a question related to OP's solution to Is constexpr useful for overload.
Basically, he used
template<class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
and
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type
f(T&& n) { ... }
to know whether f() has been called with is a compile-time variable (e.g. literal: f(42)) or an lvalue (e.g. local variable: f(argc)) as its argument.
Q: How does that work ? (I expected, in both calls, that the first overload would be called (i.e. std::is_arithmetic<T>::value == true)
Here is a full example:
Run It Online
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;
template<class T>
constexpr
typename std::enable_if<std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "compile time" << endl;
return 1;
}
template<class T>
typename std::enable_if<!std::is_arithmetic<T>::value,
int>::type
inline f(T&& n)
{
//cout << "run time" << endl;
return 0;
}
int main(int argc, char* argv[])
{
const int rt = f(argc);
constexpr int ct = f(42);
cout << "rt: " << rt << endl;
cout << "ct: " << ct << endl;
}
A template function of the form
template <typename T>
void func(T&& t);
looks as if it takes an r-value reference. But in actual fact T&& here is what Scott Meyers calls a universal reference, otherwise known as a forwarding reference. Different things can happen depending on the value category of the argument. Let's have a look at each case:
t is a non-const lvalue, for example
int i = 0;
func(i);
In this case, T is deduced to be an lvalue reference to int, that is, T=int&.
t is a const lvalue, for example
const int i = 1;
func(i);
Similarly, in this case T is deduced to be const int&.
t is an rvalue, for example
func(1);
In this case, T is deduced to be int just as we might have expected
Exactly why these deductions happen this way is to do with the rules for reference collapsing; I highly recommend reading Scott Meyers' article on the subject if you're interested.
The last case above also illustrates the point that in C and C++, literals (except string literals) are always rvalues.
What does this have to do with the enable_if? Well if your f is called with an integer literal, then T is deduced to be plain int. Obviously, is_arithmetic<int> is true, so the second function gets SFINAE'd out and the first is called.
However, when called with an lvalue, T is deduced to be (const) int&. A reference is not arithmetic, so the first function disappears leaving only the second to be called.

how to decay array type to const pointer type in C++?

I would like to automatically generate const accessor function for given member but I struggle with arrays. It is possible to "decay" array type to a pointer, but I do not know how to make type of pointed value const? Any obvious method of adding const will only apply the pointer. Of course, I can make specialised accessor for array types, but it is not ideal solution. Returning const pointer to const value would also be acceptable. This is example of incomplete accessor:
auto foo() const -> const typename std::decay<decltype(foo_)>::type { return foo_; }
If you intend to get the address of a member array, simply qualify it as const
#include <iostream>
using namespace std;
struct fooType {
};
class MyClass {
public:
fooType foo_[2];
auto foo() const -> typename std::decay<const decltype(foo_)>::type
{ return &foo_[0]; }
};
int main() {
MyClass classObj;
classObj.foo();
return 0;
}
http://ideone.com/PjclAf
Edit:
The documentation states that
Applies lvalue-to-rvalue, array-to-pointer, and function-to-pointer
implicit conversions to the type T, removes cv-qualifiers, and defines
the resulting type as the member typedef type. This is the type
conversion applied to all function arguments when passed by value.
(emphasis mine)
The important takeaway here is that std::decay() always act to "simulate" a pass-by-value mechanism with the type you're feeding it. Cv-qualifiers are dropped iff they can be dropped in a pass-by-value call, not if they actually define the resulting type.
Take the following example:
#include <iostream>
#include <type_traits>
template <typename T, typename U>
struct decay_equiv :
std::is_same<typename std::decay<T>::type, U>::type
{};
void function1(int happyX) {
// happyX can be modified, it's just a local variable
happyX = 42;
std::cout << happyX << std::endl;
}
void function2(const int *ptrByValue) {
// ptrByValue can be modified, however its type is 'const int' and that CANNOT be modified
ptrByValue = (const int*)0xDEADBEEF;
std::cout << ptrByValue << std::endl;
}
int main()
{
std::cout << std::boolalpha
<< decay_equiv<const int, int>::value << std::endl // cv-qualifiers are dropped (pass-by-value)
<< decay_equiv<const int[2], int*>::value << std::endl; // cv-qualifiers here CANNOT be dropped, they're part of the type even if passed by value
const int myConstValue = 55;
function1(myConstValue);
const int myArrayToConstValues[2] = {4,2};
function2(myArrayToConstValues);
return 0;
}
http://ideone.com/AW6TJS
In your example you're asking for a constant return value (you can't modify the address of the first element) but asking in the trailing return type for a non-const one, that's why the compiler is complaining and what I just wrote is the reason why the const cannot be dropped by std::decay(): it is part of the type even in a pass-by-value situation (e.g. function2()).