Compile time tricks

Started by
7 comments, last by Green_Baron 4 years, 6 months ago

It seemed best to start another thread for this instead of derailing the thread the conversation started in and maybe to get some other opinions.

10 hours ago, a light breeze said:

... but the sizeof(ARRAY)/sizeof(ARRAY[0]) trick for finding the length of an array has no place in C++.

5 hours ago, MagForceSeven said:

... but I'm curious as to what the alternative would be? I'm not aware of any standard language feature that has replaced this particular trick.

...

The only one that I can think of would be never use static arrays and only ever use std::vector (or whatever dynamic array is available in your engine, like TArray in UE or whatever vector replacement EA STL has) and that just seems misguided. While I usually use the dynamic array that I have available, there are cases where that's just overkill or doesn't meet the compile-time requirements required.

I've mostly used this in combination with static_assert to validate (at compile time) that some static array size matches the length of an enumeration.

The old static assert trick was to define an array that had size 0 or 1 based on a compile-time boolean condition. If the condition was false, the array would be sized to 0 which is illegal and caused a compile error. That trick no longer has a place in modern C++ precisely because static_assert is now a language feature that directly provides the feature.

1 hour ago, a light breeze said:


template<class T, std::size_t N> constexpr std::size_t array_size(T(&)[N]) noexcept {
  return N;
}

Without the constexpr and noexcept, this has worked for as long as C++ has had templates.

That's definitely clever, but it feels like you're just trading one compiler trick for a different compiler trick. Do you think this one is better because it's somehow "more C++" than the sizeof version?

I have two issues with this code. Firstly I think it's too complicated and clever for it's own good. Even setting aside the new-ness of constexpr, this requires an understanding of templates and template parameter deduction that can take a while to understand. I have a hard enough time with other programmers doing templates right with trivial type deduction that this could make their head explode. Secondly, now that this is a function it's got to live somewhere which means either duplicating it in every header that uses it (bad) or putting it in it's own header (or a utilities header) and including it. This point is more personal preference than anything else since it feels like such a trivial operation to include a header for. I've definitely seen code that wraps the sizeof trick in a macro, but I think that's silly too since that has the same dependency problem as this function. I don't think many people would make a macro/function to wrap "+= 2" just because it happens to occur in multiple places in the code.

I don't want to come off as argumentative here @a light breeze. I'm not claiming to be right or that you're wrong or that there's even a right or wrong to be decided here. I'm honestly just very interested in the why of how people decided the things they find acceptable or not. And when someone uses a phrase like "has no place" I immediately want to explore that!

--Russell Aasland
--Lead Gameplay Engineer
--Firaxis Games

Advertisement

I was a little surprised by the solution the pulls all registers on C++ keywords as well ?

My naive 5 cents:

The sizeof(array)/sizeof(element) thing can be regarded as being C-style, like is using pointer arithmetics *(&array[lastElement]) - array. I think (pls. correct me) that C++ knows variable length arrays only through extensions, like GNU. There are people who say that using raw pointers is a nono is C++ (i use them, i am a rebel). And they crash so nonchalantly.

In C++ there are std::array (length known at compile time) or std::vector (dynamic length). The C way is not barred in C++ of course, at least not technically. Or so.

Edit: i see you have Firaxis Games in your signature. Good old SMAC was one of my first game addictions ... ?

38 minutes ago, MagForceSeven said:

That's definitely clever, but it feels like you're just trading one compiler trick for a different compiler trick. Do you think this one is better because it's somehow "more C++" than the sizeof version?
 

The main advantage is that it's fool-proof, i.e. it fails at compile-time if you pass it a non-array type such as a pointer, whereas the sizeof trick silently produces incorrect results.


// This function looks like it takes a fixed-size array as argument, but it really takes a pointer.
void f(int a[10]) {
  std::cout << sizeof(a) / sizeof(a[0]) << "\n" << std::flush; // Silently produces incorrect result.
  std::cout << array_size(a) << "\n" << std::flush; // Fails at compile-time.
}

 

1 hour ago, MagForceSeven said:

That's definitely clever, but it feels like you're just trading one compiler trick for a different compiler trick. Do you think this one is better because it's somehow "more C++" than the sizeof version?

The division uses "random" artihmetics to calculate the size, being prone to errors on all ends.

The other version uses the information embedded into the type of the array to retrieve the actual lenght of any array that is passed in. Also, it requires an actual compile-time length to be passed in (otherwise resulting in a compile error), while you could give the other function a pointer of unknown length and it would give you (i belive) '1' as the result.

Learning something here ?

But is unsigned integer division really "random" ? And shouldn't the array length always be a multiple of its elements ?

40 minutes ago, a light breeze said:

The main advantage is that it's fool-proof, i.e. it fails at compile-time if you pass it a non-array type such as a pointer, whereas the sizeof trick silently produces incorrect results.

Hmm, that's definitely an interesting case I hadn't considered. I've only ever using it at scopes where an actual array declaration is present such that the pointer decay presented there can't possibly have occurred.  A run-time scope like that shouldn't be hiding the decay and making it look like it enforces a restriction that it doesn't really, but it's hard to truly argue the validity of example code. Even if the function was written better it doesn't change the fact that one solution generates a compile error and one doesn't.

17 minutes ago, Juliean said:

The division uses "random" artihmetics to calculate the size, being prone to errors on all ends.

Like Green_Baron, I'm not sure I understand your use of the word "random" here. It's elementary or middle school math: Joe has 10 pounds of watermelon. If each watermelon weighs 2 pounds, how many does he have? It's just math. Is it just random because a person is deciding to do math on two numbers? Possible user error, of course but I wouldn't go as far as to say "prone" to error. It's not one I can say I've ever seen made using the sizeof solution.

I appreciate both your insights, though I'm honestly not sure that it'll change anything for the very narrow use-case I personally am solving for. But it's a good discussion that helps another programmer out. This isn't to close this conversation, I'd love to keep going if there's more to discuss (such as Juliean's reply to my question).

1 hour ago, Green_Baron said:

Edit: i see you have Firaxis Games in your signature. Good old SMAC was one of my first game addictions ... ?

SMAC is one of my favorites too, but I've only been working there for about 5 years so I didn't contribute to that one. At Firaxis I first worked on XCom 2 and then the two(ish) expansions War of the Chosen and the Tactical Legacy Pack. I forgot about my signature (had them turned off for some reason) so I'm not even a systems programmer anymore, jumped back to gameplay for this next project.

--Russell Aasland
--Lead Gameplay Engineer
--Firaxis Games

The other advantage of the function template is that it can be overloaded, so it will still work if you ever decide to change to a different container. And for anyone new to C++ 'std::size' should be easier to remember than the sizeof trick.

And all stl containers have the size() method.

But c-style arrays are still needed by the apis. I find them pretty economical, simple and fast for that purpose (elements of vectors and matrices and colour values for example). In these cases, i'd rather bite the bullet of using sizeof than starting to convert data to obtain a pointer to a contiguous array of let's say 4 floats ... not that i don't trust the compiler but i know me ...

Sure, a huge pack of vertices or indices is better herded in an std::vector or std::array with all the power of the stl, no question.

Just an opinion ?

This topic is closed to new replies.

Advertisement