The return type of a vector is a size_t not an int (contrary to how the FSO code base handles them) so don't cast size_t to int without a good reason. From what I see there isn't a good one (even FSO doesn't have a good reason other than the
coders decided that using negative numbers was good for error codes (which honestly has better alternatives)).
But then how to compare the size_t with a regular int? VC++ doesn't like if I don't cast it.
Yes all compilers don't, but there is no reason that you can't declare the counter as a size_t rather than an int. Something like the below.
//A robot?
for (size_t k = 0; k < Bots.size(); k++)
{
if ( Bots[k].isHere(i,j) )
{
drawthis = '+';
}
}
Obviously there will be times that it is necessary to mix a size_t and an int in a comparison and at that time make sure that you check that the numbers are in a valid range to compare with each other. That is, make sure that the int is not negative and that the size_t is not above MAX_INT (2^31, which is 2,147,483,647, in 32-bit mode), otherwise you get funky things happen like below.
The size of int is 4
The size of size_t (unsigned int) is 4
** The number in [] is the value that is written in the code.
1[ 1] == 1[ 1] == true (Good, true)
1[ 1] > 1[ 1] == false (Good, false)
1[ 1] < 1[ 1] == false (Good, false)
2[ 2] > 1[ 1] == true (Good, true)
-1[ -1] < 1[ 1] == false (Fail, true)
-1[ -1] > 1[ 1] == true (Fail, false)
1[ 1] < 4294967295[ -1] == true (Fail, false)
1[ 1] > 4294967295[ -1] == false (Fail, true)
1[ 1] == 4294967295[ -1] == false (Good, false)
2147483646[ 2147483646] == 2147483646[ 2147483646] == true (Good, true)
2147483647[ 2147483647] == 2147483647[ 2147483647] == true (Good, true)
-2147483648[ 2147483648] == 2147483648[ 2147483648] == true (Good, true)
-2147483648[-2147483648] == 2147483648[ 2147483648] == true (Fail, false)
-2147483647[ 2147483649] == 2147483649[ 2147483649] == true (Good, true)
-2147483646[ 2147483650] == 2147483650[ 2147483650] == true (Good, true)
The size of int is 4
The size of size_t (unsigned int) is 8
** The number in [] is the value that is written in the code.
1[ 1] == 1[ 1] == true (Good, true)
1[ 1] > 1[ 1] == false (Good, false)
1[ 1] < 1[ 1] == false (Good, false)
2[ 2] > 1[ 1] == true (Good, true)
-1[ -1] < 1[ 1] == false (Fail, true)
-1[ -1] > 1[ 1] == true (Fail, false)
1[ 1] < 4294967295[ -1] == true (Fail, false)
1[ 1] > 4294967295[ -1] == false (Fail, true)
1[ 1] == 4294967295[ -1] == false (Good, false)
2147483646[ 2147483646] == 2147483646[ 2147483646] == true (Good, true)
2147483647[ 2147483647] == 2147483647[ 2147483647] == true (Good, true)
-2147483648[ 2147483648] == 2147483648[ 2147483648] == false (Fail, true)
-2147483648[-2147483648] == 2147483648[ 2147483648] == false (Good, false)
-2147483647[ 2147483649] == 2147483649[ 2147483649] == false (Fail, true)
-2147483646[ 2147483650] == 2147483650[ 2147483650] == false (Fail, true)
/* This is a scratch program to demonstrate int vs size_t (unsigned int).
It is not an example of good programming practice. */
#include <stdio.h>
#define TO_STRING(x) #x
#define COMPARE(i,op,u,e) printf("%11d[%11s] %2s %11u[%11s] == %5s (%s, %5s)\n",\
(int)(i),\
#i,\
TO_STRING(op),\
(size_t)(u),\
#u,\
(int(i) op size_t(u))?"true":"false",\
((int(i) op size_t(u)) == e)?"Good":"Fail",\
#e)
#define EQ ==
#define LT <
#define GT >
int main(int, char**) {
printf("The size of int is %d\n", sizeof(int));
printf("The size of size_t (unsigned int) is %d\n", sizeof(size_t));
printf("** The number in [] is the value that is written in the code.\n");
COMPARE(1,EQ,1,true);
COMPARE(1,GT,1,false);
COMPARE(1,LT,1,false);
COMPARE(2,GT,1,true);
COMPARE(-1,LT,1,true);
COMPARE(-1,GT,1,false);
COMPARE(1,LT,-1,false);
COMPARE(1,GT,-1,true);
COMPARE(1,EQ,-1,false);
COMPARE(2147483646,EQ,2147483646,true);
COMPARE(2147483647,EQ,2147483647,true);
COMPARE(2147483648,EQ,2147483648,true);
COMPARE(-2147483648,EQ,2147483648,false);
COMPARE(2147483649,EQ,2147483649,true);
COMPARE(2147483650,EQ,2147483650,true);
return 0;
}
The other thing I just noticed, which is a bit more advanced topic, is that Bots is a
std::vector which means is a a container class, which means it has an iterator interface, which quite often results in better performance. It would look something like this:
//A robot?
for (std::vector<Robot>::const_iterator iter = Bots.begin();
iter != Bots.end(); iter++)
{
if ( (*iter).isHere(i,j) )
{
drawthis = '+';
}
}
Though for that to compile we need to change the prototype (and the definition) of
Thing::isHere(int,int) so that it is a
const function. That is, it promises that it will not change the object that it is acting upon.
bool isHere (int isx, int isy) const;
This is also a slightly more advanced topic, but one that FSO could learn a lot from. FSO does not use const nearly enough for whatever reason (though admittedly because FSO uses so much global data anyway it is a fairly moot point because const cannot protect that data because
someone needs to be able modify it). A large number of our bugs stem from functions that seemingly should not need to change anything, but do.
Strictly main also has two more arguments (int argc, char** argv).
Are these the command-line arguments? Should I include support for them even if they're not used anywhere?
Yes, they are the command line arguments. It doesn't hurt to include them because you will have to accept command-line arguments at some point. Obviously it is up to you, but some compilers will always complain about it being missing, others will complain when you set them to pedantic mode.
Because pedantic mode will also complain that you declared them but don't using them, you can declare main as:
int main(int, char**)
or you can define a macro that does nothing so that you can name the args but the compiler never sees the name declared. The end result is the compiler seeing the same thing as the main prototype above, but we as coders get the benefit of knowing what each parameter should be used for. Obviously this is a bit overkill for main, and is more helpful when you are talking about functions that not everyone knows the names of the parameters are or for, like in a library or in large code base like FSO. A good example of this would be a flags argument that is part of the standard prototype but not every implementation needs or uses the flags argument.
/* Used to flag a function arg as not used so that
the compiler does not whine about a variable that
is declare but unused. */
#define UNUSED(x)
int main(int UNUSED(argc), char** UNUSED(argv))