c++ complaining
In C++ you can use the address-of operator & to find the address of any lvalue†. However, due to operator overloading this is not necessarily true. So what do you if you overload &?
C++, as usual, has introduced a solution for the problem it created with the standard library function std::addressof. How is this implemented, you may ask? Well, as you have to do is write the following elegant, readable, and simple snippet:
template<typename _Tp>
inline _Tp*
__addressof(_Tp& __r) _GLIBCXX_NOEXCEPT
{
return reinterpret_cast<_Tp*>
(&const_cast<char&>(reinterpret_cast<const volatile char&>(__r)));
}
† this is actually a lie. function names are considered lvalues. However, if you overload a function, then the program fails to compile if you provide that name to std::addressof because C++ is a poorly designed language that makes no sense.
re: c++ complaining, wall of text
I lied I learned how this works.
template<typename _Tp> is C++'s overly-convoluted implementation of generic programming. The need for generic programming is very simple to motivate and in better languages these simple cases are extremely easy to understand, see for example https://www.youtube.com/watch?v=EcCTIExsqmI for how TypeScript handles some simple cases. In this case _Tp is just some generic type so that we don't have to define near exact copes of this function for int and long int and long long int and std::string and char and etcinline tells your compiler to try "inlining" any invocation of the function being declared. This means that the compiler will consider replacing any invocation of the function in your code with the function body. For small one-liners functions like this one the increased memory size of your resulting program isn't too big and avoiding the overhead of maintaining a call stack is worth it. It is important to understand that the compiler is not guaranteed to inline your function--the compiler may decide that inlining in some cases is not a a performance boost. (In fact, C++ compilers have always been allowed to inline any function is deems inline-able even if the keyword is not present. Modern compilers are extremely smart with inlining and it is no longer recommended to ever use the inline keyword unless you have profiled your code and determining that a small function is a bottleneck.)__BLIBCXX_NOEXCEPT is a macro defined specifically by the GCC compiler. This macro will not work on any other compiler unless you define the macro yourself. It is defined as follows:#ifndef _GLIBCXX_NOEXCEPT
# if __cplusplus >= 201103L
# define _GLIBCXX_NOEXCEPT noexcept
# define _GLIBCXX_USE_NOEXCEPT noexcept
# define _GLIBCXX_THROW(_EXC)
# else
# define _GLIBCXX_NOEXCEPT
# define _GLIBCXX_USE_NOEXCEPT throw()
# define _GLIBCXX_THROW(_EXC) throw(_EXC)
# endif
#endif
noexcept is a keyword introduced in C++11 that flags to the compiler that the function is not expected to throw any errors which allows the compiler to optimize the function a bit. (If a function declared with noexcept does end up throwing an error your program will simply terminate.) It has since deprecated the throw keyword which was used for similar purposes (you would use it like throw(EXCEPTION_1, EXCEPTION_2) to indicate that the function was only supposed to throw one of the two listed exceptions), except the program would exhibit undefined behavior if a function declared with throw() threw an any error because C++ is a bad language. The GCC compiler introduced this macro so that you could write code that would use noexcept if you are compiling to C++11 and throw() if you are using earlier versions of the language.
reinterpret_cast is a cast that does not change the bits in the data being casted. Ordinarily, if you cast a float to an int, your computer will perform a calculation to change that float into an int with a value that is as close to the original float value as possible. But if you use reinterpret_cast to cast a float to an int, the bits used to represent the float will now represent whatever number that an int on your specific computer architecture will be for the string of bits that used to represent a float. const_cast is a cast that allows you to cast of const-qualified value into one without the const qualification.So, the point here is that we want to cast whatever data type is given to the function as a char. Why a char? There are two reasons. The first is that the &* operator cannot be overloaded for built-in primitive types like char, int, etc so we can use & to take the address of them. The other reason is because data in your computer has to adhere to what are called "alignment requirements". That is, computer architecture demands that the number of bits between certain data types be a certain amount.
Suppose you had a variable that was some 8-bit data type and, in storage, immediately to its right is are 32 bits used to represent a 32-bit integer. Now suppose you casted your 8-bit data type to a 32-bit integer. Well, now you have an overlap in bits! This is no good because if we write to both variables, we will actually be manipulating both variables in unexpected ways. So reinterpret_cast won't actually look at the same the bits as before, and it be looking at a new spot where it won't have this "overlap". We can avoid this problem by casting to a new data type that won't cause this sort of overlap to occur. It so happens that char is tied for the least-strict alignment requirements of any data type in your computer architecture so we will avoid any overlapping problems entirely by casting to char.
So what we do is reinterpret_cast whatever type was given as a const volatile char reference. Why a reference? Because we are only allowed to use & on lvalues, but the results of casts are almost always treated as rvalues, which the one exception being casts to lvalue refererences. (This is likely to be consistent with the fact that functions which return lvalue references are treated as lvalues, ie, all computations of lvalue references are treated as lvalues). Why are we using the const and volatile references? Because the generic value given to the function might have const and volatile qualifiers and C++ does not allow reinterpret_cast to strip these qualifiers away from a value.
Now, why do we have the intermediary const_cast<char&> where we strip the volatile and const qualifiers? Why don't we just do return reinterpret_cast<_Tp*>(&reinterpret_cast<const volatile char&>(__r));? Well, according to cppreference, "If the operand is .... type T, & creates and returns a prvalue of type T* with the same cv qualification". Hence, the cast back into a reinterpret_cast<_Tp*> results in an attempt to strip a cv-qualification. const_cast is one of the few situations where C++ allows you to explicitly strip a cv-qualification, so we use it. We could also use an ordinary C-style cast ((char&) instead of const_cast<char&>) but C-style casts are considered unfashionable.
In short, we are using a special kind of cast that does not change the bits present in the underlying data to cast the given value into a built-in type that won't cause alignment issues and can be used with & without fear of overloading. We have use a lot of intermediary casts in order to satisfy the C++ compiler when a cv-qualified value is given to the function. The function uses the inline and noexcept/throws() keywords so that the compiler will try to super-optimize the function. We use C++'s implementation of generic programming so we can define the function for all possible types.
re: c++ complaining, wall of text
@aescling Yes, the minimum size of of a char is 1 byte. I am fully aware of that.
There is no type in C++ that can be smaller than 1 byte. In fact, every type is some stored as a integral number of bytes. So any type can be reinterpreted as a char string. I don't see what the misconception is here.
re: c++ complaining, wall of text
@wallhackio if a char refurence is an alias to a preexisting char, then how can you cast data larger than one byte to a char refurence?
re: c++ complaining, wall of text
@wallhackio in your wall of text you described the function’a casting to char& as casting to char. but casting to char and casting to char& are not the same thing. i am stubbornly asking why casting to char is impawsible to draw your attention to this mistake
re: c++ complaining, wall of text
me: how does this work if you cast to char?
you: this is how casting to char& works
me: but you said the function is casting to char, which is not that
re: c++ complaining, wall of text
@aescling the image i drew is what would happen if you casted to char.
re: c++ complaining, wall of text
@aescling reinterpret_casting to char or to char& is the exact same thing except whether or not the result is treated as an lvalue or rvalue.
re: c++ complaining, wall of text
@wallhackio that is an extremely meaningful diffurence!!
re: c++ complaining, wall of text
@aescling you are making the rookie mistake of assuming that C++ makes sense
re: c++ complaining, wall of text
@wallhackio i am looking at the C++ Refrence page fur reinterpret_cast and it’s implying you can’t even cast to char: https://www.en.cppreference.com/w/cpp/language/reinterpret_cast.html
re: c++ complaining, wall of text
@wallhackio no
re: c++ complaining, wall of text
@aescling why not
re: c++ complaining, wall of text
@wallhackio that only makes sense if you are casting to a char * or char&. otherwise you will store a copy of that truncated data in a new location fur the new char
re: c++ complaining, wall of text
@aescling reinterpret_cast tries not to make a copy of anything. it is looking at the same region of memory but interpreting it differently. that's what it does, that's the whole point
re: c++ complaining, wall of text
@aescling according to this stackexchange answer https://stackoverflow.com/a/5924278/22334683
"reinterpret_cast<T&>(x) is the same thing as *reinterpret_cast<T*>(&x)"
re: c++ complaining, wall of text
@wallhackio that only makes sense if you try to cast arbitrary data to char&!! trying to cast to char makes no sense!!!!!!
re: c++ complaining, wall of text
@wallhackio so you can reinterpret_cast to char * or char&
doing that cast to char is not what you are describing
re: c++ complaining, wall of text
@wallhackio you are describing a char pointer and telling me it is a char
re: c++ complaining, wall of text
@aescling you are right, you can only reinterpret cast to a char pointer or reference. i was mistaken about the existence of reinterpret casts to char
I don't understand how the result is a char pointer. It is not. It is a char reference. References are aliases. They are like macros to the original thing. They aren't a number representing a location in memory. They are another name for the variable itself
re: c++ complaining, wall of text
@wallhackio it does not make sense fur it to act like anything else than a char pointer so idk why they use an explicit char refurence instead of explicit char pointer
re: c++ complaining, wall of text
@aescling again I return to the statement "reinterpret_cast<T&>(x) is the same thing as *reinterpret_cast<T*>(&x)"
A reinterpret cast to a reference type is equivalent to a dereferenced reinterpret cast of a pointer. It is very explicitly not a pointer.
re: c++ complaining, wall of text
@wallhackio jesus christ that just makes the function as defined utterly mystifying to me
re: c++ complaining, wall of text
@aescling again I return to the statement "reinterpret_cast<T&>(x) is the same thing as *reinterpret_cast<T*>(&x)"
A reinterpret cast to a reference type is equivalent to a deferenced reinterpret cast of a pointer. It is very explicitly not a pointer.
re: c++ complaining, wall of text
So, the point here is that we want to cast whatever data type is given to the function as a char. Why a char?
you are saying the function is casting arbitrary data to char. you cannot cast arbitrary data to char exactly because a char (typically) only holds one byte. so, the function cannot actually doing what you wrote in your description that it is doing
re: c++ complaining, wall of text
@aescling A reference is treated as an alias to some other preexisting value. A char reference is not a pointer. It is an alias for some preexisting char.