开发者

Is a function reference sizeof portable?

开发者 https://www.devze.com 2023-01-01 11:25 出处:网络
Looking for a way to do a portable, safe, elements count for c-style arrays, I found this solution: template <typename T, unsigned N>char (&arrayCountofHelper(T(&)[N]))[N];

Looking for a way to do a portable, safe, elements count for c-style arrays, I found this solution:

template <typename T, unsigned N>  char (&arrayCountofHelper(T(&)[N]))[N];
#define ARRAY_COUNTOF(arr) (sizeof(arrayCountofHelper(arr)))

It seems like arrayCountofHelper is actually a reference to a function, and the macro ARRAY_COUNTOF uses the fact that the size of a function is the size of it's returned type.

It works just fine.

However, when I tried to check if it's portable, I didn't find any evidence for that. I couldn't find any reference to sizeof(function reference) in the standard (14882/1998) and, in fact, I am not entirely sure I am allowed to even create a function reference any more (although the standard does mention it several times).

So, does someone know where in the standard I should look? (Or, if I m开发者_如何学Goisinterpreted the declaration some how, what is the correct interpretation?)

Thanks

Oren

P.S. (for those of you who think I didn't find the appropriate solution for my problem)

I know I can always use

#define ARRAY_COUNTOF sizeof(arr)/sizeof(arr[0])

or even

template <typename T, unsigned N> size_t arrayCountof(T(&)[N]) {return N;}

but the first will not check if arr is a array (or pointer) and the second is not usable inside static_assert.

(and I would have used std::tr1::array or std::vector, but this is a legacy code I am maintaining)


We will pick this apart loosely, I'm using INCITS+ISO+IEC+14882-2003. I'll quote the small stuff, but some of the more complex stuff is too large to quote.

sizeof is defined in §5.3.3, and it says (abridged):

The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id.

In other words, it yields the size of a type in bytes or finds the type of an expression and yields the size of that. We don't have a type, we have the expression arrayCountofHelper(arr).

You can dissect this expression by looking at the definitions of primary-expression and postfix-expression as defined in §5.1 and §5.2 respectively. You will find it is a postfix-expression and fits the requirements of a function call (§5.2.2).

Now back to sizeof. We only care about the type of this function call expression (so we can yield the size of it), and §5.2.2/3 says:

The type of the function call expression is the return type of the statically chosen function [...]. This type shall be a complete object type, a reference type or the type void.

So we need to find the return type of the function that would be called (remember, this is all unevaluated) with arrayCountofHelper(arr). arrayCountofHelper is a function template which we would be instantiating (§14.7), so we need to do that before we have an instantiated function to get the return type of.

All the template parameters need to have values (§14.8.2), and by using the rules defined in §14.8.2.1 we will find T and N by matching the array passed to the function with the functions parameter (which is a reference to an array). (For example, if arr was int[10], T would be int and N would be 10.) Once we have those, the function can be instantiated.

Once instantiated, the return type of the function would be char(&)[N]*, a reference to an array of N char's. (If you need help parsing, see §8.3.5. There are also questions on SO on how to parse "complex" types.) So now we've found the type of the expression, we must take the size of it.

§5.3.3/2 defines how sizeof works with references and arrays (emphasis mine):

When applied to a reference or a reference type, the result is the size of the referenced type. When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array. The size of a most derived class shall be greater than zero (1.8). The result of applying sizeof to a base class subobject is the size of the base class type.70) When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.

The size of a reference type is the size of its referenced type, so we need to size of char[N]. The size of this is N * sizeof(char). char is fundamental as it is the smallest type there is; that is, sizeof(char) is always one. (§5.3.3/1) So the size yielded by this expression is 1 * N, or what we wanted the whole time: N.

And that's how it works.


The reason this one is preferred over your last example of arrayCountof is because the result of sizeof is a constant expression, so can be used in places where a constant expression is required.

It should be noted that in C++0x, we can get our clean no-macro syntax with:

template <typename T, unsigned N>
constexpr size_t arrayCountof(T(&)[N]) {return N;}

* The reason the function return type is a reference to an array and not an array is simply because you cannot return arrays. If you could, either choice would suffice.


Oren,

This is not a function reference. This is a function call expression, and it has a type - the type of the result. Sizeof can be applied to any expression that has a type.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号