Entity component systems increase performance when you use arrays for your components and perform functions on everything in those arrays. Because your array is containing only the component data, and laid out consecutively in memory, you can get more of it in cache at once, with no dereferencing required.
So you are saying you want to add a vector of components to CvPlayer and then loop the components. It seems doable and yes it will most likely be faster if you only need the component data. However if you have CvUnit and need data from the component, it will be slower. Also you have the extra overhead of linking the component to the CvUnit, including the risk of bugs (linking to wrong vector index etc). Also if the component points to CvUnit and that pointer is used during the loop, you will have gained nothing.
It's not a bad idea, but you will likely need a significantly sized component for this to work well.
Unions on the other hand will not have the overhead of linking two different locations in memory, hence you won't have the risk of going out of sync. You will need to find mutually exclusive data for them to work though.
If we look at the question, which started this, which is to store population in a settler. Following the vanilla approach, we can add the variables and leave the data 0 for non-settlers. This will waste memory for non-settlers. Even worse, this wasted memory is usually saved, meaning it's bloating savegames in addition to runtime memory.
Now if we make a struct and move some existing data into that struct, then we will end up with something, which compiled is the same as it is right now if we have one instance of that struct as member data. Say we add city bombardment data. Now if we place that struct in a union, it will still not change anything. If we make another struct and add population, if the population struct isn't bigger than the bombardment struct, then the population data is saved in CvUnit without using more memory. If you can find something, which is exclusive to both settlers and bombardment (worker data comes to mind), then add a struct for that too and suddenly it will use less data than it is using currently even if it is storing more data. Yes it will waste memory if the structs aren't of the same size, but it will waste less memory than the current design.
You will need logic to the get/set functions of the union data though. This can be done with checking unitcombat in some switch case setup, assert check that only settlers ask for population, template int parameter to function calls or something else. There are multiple ways to do this.
I wonder if you can do something clever with the structs where the get/set calls the structs and the "wrong" structs returns the default value (often 0). This would however move the problem to calling the correct struct. Maybe there should be just one struct, which then takes a template int parameter, hence one struct for each unitcombat. If done cleverly, that could move a lot of the switches between the types to compile time.
This quickly gets complex and it's no wonder vanilla didn't go this way. It's too easy to just waste memory.
It's also worth considering which kind of variables you store. Do you really need 32 bit to store population? If it's always max 15 of each group, 4 bits will be enough and bitfields can be used. That's another way to reduce memory usage. In fact putting all the bools together and use :1 can save a bunch of memory because then you can have 32 bools in what the compiler will normally assign to a single bool.
There is one more way to save memory, which is to use EnumMap and EnumMap2D (array of EnumMaps), the two classes I'm currently working on. They get a type (usually the length of an xml file, but can be MAX_PLAYERS etc) at compile time based on template parameters, reads the length at runtime (doesn't have to know the number of units at compile time etc) and then it has set, get and add functions. Kind of similar to the functions you already use, except it has strict typing in arguments and importantly here: automated memory management.
What it does is that it assumes everything to be the default value while containing a NULL pointer. It doesn't allocate the array until a non-default value is written to the array. It releases the memory when calling reset() or the deconstructor (yeah, no memory leaks). There is one more way to release memory, which is to call hasContent(), which will release the memory if everything is of the default value. Since saving the array calls hasContent(), autosave becomes some sort of garbage collector.
Also it can reduce the size of the saved variables too. If you want to make an array of PlayerTypes, the compiler will go "MAX_PLAYERS <= 128" and since it fits in a char, it will make the array to be a char array internally. An array of PlayerTypes will compile into an int array, which use 4 times the amount of memory.
I will however point out that I better finish developing EnumMap before porting it to other mods. It's still new and under development, but it's near completion, kind of like beta state.
If you are going to aim at reducing memory usage, I better keep an eye out for what you are doing. You might come up with something interesting.
Oh and do make sure you know precisely what you are storing in memory. I learned this week that Colonization stores yield modifiers for each player in each area. That's 11.5 kB of data for each area, which is always allocated and saved. The reason why this is bad is because the set function is never called, meaning it's 11.5 kB of only 0 values and whenever it's called is is used as right hand value in +=, meaning it doesn't do anything. Changing it to an EnumMap2D reduced it to 4 bytes for each area without killing the feature (we might use it in the future). Sometimes memory usage isn't in the most obvious locations.