I'm writing a C++ program that uses an integer value to differentiate between a large number of derived class objects. The top-level class stores the integer in the constructor, and has a public method to query it; the idea is to make it so I can quickly tell objects apart when I store them in an array using pointers typed to their top-level class.
I'm using #defines to provide plain-English names for some of the IDs; I want to be able to reference them from (my) memory without the added computational load of using string-based identifiers to find them. I'm reluctant to make a completely static declaration in case I want to add or change identifiers later (for example, some are allocated by block, and I may someday need to increase the block size). I've started using #define to increment the index by one.
I think examples of what I'm doing might be easier:
#define OBJ_FRED 0
#define OBJ_CATHY OBJ_FRED + 1
#define OBJ_JOE OBJ_CATHY + 1
#define OBJ_TRIPLETS_START OBJ_JOE + 1
#define OBJ_NANCY OBJ_TRIPLETS_START + 3
// Note that there will potentially be a lot of these.
To answer your question as asked, yes the preprocessor will substitute
0 + 1 + 1 + 1 + 3. That's what the processor does - it expands macros and does text substitution.
However, the processor is only one phase of compilation. Although (strictly speaking) not required to, later phases of compilation will typically evaluate constant expressions e.g. turn
0 + 1 + 1 + 1 + 3 into
6, rather than leaving the evaluation to be done at run time. It is a pretty basic optimisation, and most decent-quality compilers implement it (albeit, possibly as an optional optimisation).
There are implementation-specific limits on the size of code statements (the standards describe lower bounds on those limits). A consequence is that there are limits on the size of a macro expansion. In practice, it is rarely a problem - the limits most implementations support are related to available memory. And, if you are running into those limits, macro expansion is the least of your problems.
As others have said in comments (and another answer), you would be better off using an enumerated type.
You might want to look at your class design. A need for a base class to contain a field that identifies its derived classes is known as a "code smell" - something that is probably unwanted, and for which there are often better alternatives. Even if, sometimes, the smell is tolerable.