Does `sizeof` *really* evaluate to a `std::size_t`? Can it?
Take the following standard passage:
[C++11: 5.3.3/6]:
The result ofsizeof
andsizeof...
is a constant of typestd::size_t
. [ Note:std::size_t
is defined in the standard header<cstddef>
(18.2). —end note ]
Now:
[C++11: 18.2/6]:
The typesize_t
is an implementation-defined unsigned integer type that is large enough to contain the size in bytes of any object.
Granted, the passage doesn't require that size_t
is a type alias defined with typedef
, but since it's explicitly stated to be made available by the standard header <cstddef>
, I think we can take as read that failing to include <cstddef>
should remove any guarantee that size_t
shall be available to a program.
However, according to that first quote, we can regardless obtain an expression of type std::size_t
.
We can actually demonstrate both of these facts:
int main()
{
typedef decltype(sizeof(0)) my_size_t;
my_size_t x = 0; // OK
std::size_t y = 1; // error: 'size_t' is not a member of 'std'
}
std::size_t
is not visible to the program, but sizeof(0)
still gives us one? Really?
Is it therefore not correct to say that 5.3.3/6
is flawed, and that it actually has "the same type as whatever std::size_t
resolves to", but not std::size_t
itself?
Sure, the two are one and the same if std::size_t
is a type alias but, again, nowhere is this actually required.
Solution 1:
The standard just mandates that the type of sizeof(expr)
is the same type as std::size_t
. There is no mandate that using sizeof(expr)
makes the name std::size_t
available and since std::size_t
just names one of the the built-in integral types there isn't really a problem.
Solution 2:
Do not confuse the map for the territory.
Types can be named by typenames. These typenames can be built-in, they can be user-defined types, or they could even be template
parameters and refer to multiple different types depending on the instantiation.
But the names are not the types. Clearly standard does not mandate that all types have names -- the classic struct {}
is a type without a name.
std::size_t
is a typename. It names the type that sizeof(expression)
returns.
The compiler could have a canonical name for the type -- __size_t
would be one way for it to have a unique built-in canonical typename.
The standard guarantees in that clause that whatever the type of sizeof(expression)
is, once you #include <cstddef>
, the name std::size_t
now refers to that type.
In the standard, they refer to types by names. They do not say "the type that this typename refers to", but simply say "the type $NAME$". The compiler could decide that int
is another name for __int_32_fast
if it wanted to, and the standard would have no objection either.
This same thing happens with std::nullptr_t
and std::initializer_list<Ts>
and std::type_info
: use of variables of those types does not always require that the header that provides you with a name for those types be included in your program.
The traditional C/C++ built-in types all had canonical names that did not require a header. The downside is that this breaks existing code, as new typenames in the global scope collide with other identifiers.
By having "nameless types", where you can get a name for them via including a header file, we avoid that problem.
Solution 3:
As I understand it, this standard passage requires the following expression:
typeid(sizeof(0)) == typeid(std::size_t)
will always yield true
. If you use the actual identifier std::size_t
, ::size_t
or any other alias/typedef will be irrelevant as long as the identity of the type, as per std::typeinfo::operator==()
, is preserved.
The same type identity issue appears in other places of the language. For example, in my 64-bit machine the following code fails to compile because of function redefinition:
#include <cstddef>
void foo(std::size_t x)
{}
void foo(unsigned long x)
{}
Solution 4:
Yes.
The type yielded by sizeof
is some unsigned integer type; the implementation defines which one it is.
For example, on some particular implementation, the type of a sizeof
expression might be unsigned long
.
std::size_t
, if it's a typedef
, is nothing more than an alternative name for unsigned long
. So these two statements:
The type of
sizeof ...
is a constant of typeunsigned long
and
The type of
sizeof ...
is a constant of typestd::size_t
are saying exactly the same thing for that implementation. The type unsigned long
and the type std::size_t
are the same type. The difference is that the latter is accurate for all (conforming) implementations, where std::size_t
might be an alias for, say, unsigned int
or some other unsigned type.
As far as the compiler is concerned, sizeof
yields a result of type unsigned long
; the compiler (as opposed to the runtime library) needn't have any knowledge of the name size_t
.
This all assumes that std::size_t
(or just size_t
if you're talking about C) is a typedef. That's not spelled out in either the C or the C++ standard. Nevertheless, an implementation can straightforwardly conform to the requirements of the standard by making size_t
a typedef. I don't believe there's any other portable way to satisfy those requirements. (It can't be a macro or an implementation-defined keyword because that would infringe on the user's name space, and a macro wouldn't be scoped within the std
namespace.) A compiler could make size_t
some implementation-specific construct other than a typedef, but since a typedef works perfectly well, there's no point in doing so. It would be nice, IMHO, if the standard stated that size_t
is a typedef.
(An irrelevant aside: The real problem is that the standard refers to the result as a "constant". In ISO C, a "constant" is a token, such as an integer literal. C++, as far as I know, doesn't define the noun "constant", but it does refer to the ISO C definition of the term. sizeof ...
is a constant expression; it's not a constant. Calling the result a "constant value" would have been reasonable.)
Solution 5:
It is the same type but you have to include that header to use it.