There are situations where tracking all instances of a class in a container is desirable. There are existing discussions about this on Stack Exchange (e.g. 1, 2, 3). While dealing with this on my own, I realized that automatically registering instances in a container is prone to undermine const correctness.
This is due to the fact that during construction the this pointer is always considered to be non-const. As far as I know, there is no way to determine whether the currently constructed instance is declared constant or not. Consider this (minimal) example, which compiles fine and succeeds with writing 555 to inst1 without even flinching:
#include <forward_list>
template <typename TInstance>
class ObjList
{
public:
ObjList()
{
instances().push_front(static_cast<TInstance*>(this));
}
// Guard against SIOF
static std::forward_list<TInstance*>& instances()
{
static std::forward_list<TInstance*> instances;
return instances;
}
};
class SomeClass : public ObjList<SomeClass>
{
public:
SomeClass(int someData) :
someData{someData}
{}
void write(int data)
{
someData = data;
}
private:
int someData;
};
SomeClass inst0{200};
const SomeClass inst1{400};
int main()
{
for (SomeClass* instance : ObjList<SomeClass>::instances())
{
instance->write(555);
}
}
Or the more extensive example on godbolt. Others have pointed out that this is just another way to shoot yourself into the foot and a source of undefined behavior (see 4).
The primary questions are:
- Is there a way to fix this?
- Can you recommend me a different design to look into?
- Note: Unlike the example on godbolt, I must use C++14.
If there is no way to fix this, I am even OK with the fact that users can modify instances that have been declared constant via a detour (there is going to be documentation on the topic). The remaining question is then:
- If I enforce that all instances are put in a writable memory location (e.g. through adding a
mutablemember inObjList), can I continue to use the outlined approach or will this still result in undefined behavior?
[dcl.type.cv] states that you may modify mutable members of const objects but modifying const members is undefined behaviour.
You could force instances of your classes to be mutable by declaring them as members of a wrapper class rather than as derived classes.
You could make the constructor of the wrapped class private to force the use of
Wrapper<T>.