Public static members (either functions or objects) are always able to be found during lookup from an instance of that class -- so the only way to avoid it is to not offer these as static members from the type you are providing.
Due to your requirement of keeping Obj's constructor private, we need to use friendship somewhere so that an external body can provide these objects and access Obj's constructor without also being of type Obj (which is what allows for the chaining).
For this reason I suggest a secondary class or struct type so that it can be friended to allow it to call Obj's constructors:
class ObjConstants;
class Obj
{
// Note: friendship here so that ObjConstants can construct it
friend ObjConstants;
private:
int _x;
Obj(int x) : _x() {}
};
class ObjConstants {
public:
static const Obj obj1;
static const Obj obj2;
};
const Obj ObjConstants::obj1(1);
const Obj ObjConstants::obj2(2);
int main()
{
Obj o1 = ObjConstants::obj1;
}
This could also just as easily be a class containing static factory functions:
class ObjConstants {
public:
static const Obj& getObj1();
static const Obj& getObj2();
};
or even just a bunch of namespace-scoped factory functions that are each friended individually:
const Obj& getObj1();
const Obj& getObj2();
class Obj {
friend const Obj& getObj1();
friend const Obj& getObj2();
...
};
When you use an external holder (whether it be type or function) for the static objects, each access of the object returns a different type than the holder (in this case, Obj) which prevents being able to chain obj1.obj2.obj1
It's also worth noting that this approach also works with static constexpr objects. You can't define static constexpr objects in the body of its own class since the class is incomplete at that point, e.g.:
class Foo {
public:
static inline constexpr auto foo = Foo{}; // Error: 'Foo' is incomplete!
};
And so using a secondary holder type (whether it be a class or a namespace) makes it possible for the definition to be complete by that point:
class Foo { ... };
class FooConstants {
public:
static inline constexpr auto foo = Foo{...};
};
Note:
In general I'd recommend against static const objects with external linkage like this to avoid static initialization issues