Предположим, у нас есть некоторый объект Foo
, который позволяет:
cout << myFoo[3];
myFoo[5] = "bar";
Это требует шаблона проектирования прокси (подробного Скотт Мейерс здесь)
Но теперь предположим, что каждый myFoo[i]
также является экземпляром Foo
.
myFoo[7] = Foo{...};
myFoo[5] = "bar"; // Foo has a Foo(std::string) non-explicit constructor
Я близок к реализации, но не могу избавиться от одной последней надоедливой ошибки "предварительное объявление/неполный тип".
Во-первых, давайте избавимся от простого:
// x = someConstObject[4], so this must be Rvalue access
// i.e. someConstObject[4] = ... would be a contradiction / const violation
const Object operator[] (const Object& key) const {
return Object{ PyObject_GetItem(p, key.p) };
}
Вот базовый шаблон нерекурсивного прокси:
Proxy operator [] ( const Object& key ) { return Proxy{ *this, key }; }
class Proxy {
private:
const Object& container;
const Object& key;
public:
// at this moment we don't know whether it is 'container[key] = x' or 'x = container[key]'
Proxy( const Object& c, const Object& k ) : container{c}, key{k}
{ }
// Rvalue
// e.g. cout << myList[5]
operator Object() const {
return container[key]; // <-- invokes the original const [] overload
}
// Lvalue
// e.g. myList[5] = foo
const Object& operator= (const Object& rhs_ob) {
PyObject_SetItem( container.p, key.p, rhs_ob.p );
return rhs_ob; // allow daisy-chaining a = b = c etc.
}
#if 0
// I think this should come for free, as the above Rvalue handler
// ... collapses a Proxy into an Object
// e.g. myList[5] = someOtherList[7]
const Proxy& operator= (const Proxy& rhs) {
// Force resolution of rhs into Object
PyObject_SetItem( pContainerObj->p, pKeyObject->p, static_cast<Object>(rhs).p /* rhs.value->p*/ );
return rhs;
}
#endif
// ^ Note: allows:
// e.g. x = y[1] = z[2]; // <-- y[1] must return an Object
// e.g. if( y[1] = z[2] ) // <-- assigns and then checks that y[1] evaluates to true
};
Не уверен, нужен ли мне этот последний обработчик.
В любом случае, чтобы сделать его рекурсивным, нам потребуется:
class Proxy : Object {
:
А это значит, что мы больше не можем определять Proxy
внутри Object
, иначе получим ошибку компилятора "попытка создания базы из неполного типа".
Итак, давайте сделаем это. И нам также пришлось бы изменить конструктор, чтобы заполнить базовый класс, когда это возможно:
class Object::Proxy : public Object {
private:
const Object& container;
const Object& key;
public:
// at this moment we don't know whether it is 'c[k] = x' or 'x = c[k]'
// If it's 'c[k] = x', setting the base class to c[k] is going to
// either set it to the old value of c[k]
// or a None object (if it didn't have any value previously)
// we had better be certain to make sure the original c[k] overload
// returns None if unsuccessful
Proxy( const Object& c, const Object& k )
: container{c}, key{k}, Object{c[k]} // <-- might fail!
{ }
А затем, благодаря базовому классу Object
, нам больше не нужно было бы вручную обрабатывать приведение типов к объекту:
// Rvalue
// e.g. cout << myList[5] hits 'const Object operator[]'
#if 0
// it looks as though we don't need to do this given that
// we now have Object as base class
operator Object() const {
return container[key];
}
#endif
Но тут становится хреново.
Если мы переместим определение Object::Proxy
за пределы (фактически после) Object
, исходное
Proxy operator [] ( const Object& key ) { return Proxy{ *this, key }; }
... теперь выдает ошибку, потому что мы использовали неполный класс (Proxy
). Обратите внимание, что простое перемещение определения наружу не устраняет тот факт, что возвращаемый тип — Proxy
. Если бы только это было Proxy*
, мы могли бы это сделать. Но Proxy
не может.
Похоже, это Catch-22, и я не вижу никакого ясного решения.
Есть ли один?
РЕДАКТИРОВАТЬ: В ответ на комментарий, предлагающий ошибочный дизайн, имейте в виду, что Object
- это легкая оболочка вокруг указателя. Он имеет только один член данных PyObject*
.
РЕДАКТИРОВАТЬ: исходный код, над которым я работаю, можно найти здесь