Stavo progettando una sorta di parser quando pensavo che sarebbe stato bello avere un overload di operatore come questo: operator[]=(subscipt_type s, rvalue_type val)
. Ad esempio abbiamo una classe:
//collection of Foos
class FooCollection
{
public:
//assume class Foo is defined
Foo& operator[]=(int i, const Foo& val);
//.....
};
E dobbiamo assegnare un valore di indice:
Foo value;
FooCollection c;
c[42] = value;
allora l'ultima riga di questo codice si risolverà in
c.operator[]=(42,value);
Il vantaggio di questo operatore rispetto a Foo& operator[](int i)
esistente è che conosciamo il valore di assegnazione durante l'indice, quindi possiamo fare qualcosa che potrebbe non essere voluto in operator=
ordinario.
Ad esempio, ho provato a progettare un albero di analisi composto da nodi polimorfici con un'interfaccia minima. Questa è la classe base del nodo, tutti gli altri tipi di nodo sono ereditati da esso:
class AnyNode
{
public:
virtual AnyNode& operator=(const AnyNode& op)
{
//Takes almost no actions here.
//Overloaded in each subclass to take some sensible actions
}
virtual AnyNode& operator[](int i)
{
//Returns special null value of AnyNode type.
}
};
La classe AnyNode non è pura virtuale, quindi è possibile costruire AnyNode
oggetti. Uno degli elementi di sintassi che cerco di analizzare è un elenco simile a Python, che può contenere valori eterogenei e può essere sottoscritto con un numero intero. Una classe per questo:
class ArrayNode : public AnyNode
{
public:
AnyNode& operator[](int i)
{
//Returns a reference to i-th element
}
};
Quindi cosa succederà quando proveremo ad assegnare qualcosa alla cella dell'array, che non è stata assegnata prima e il suo tipo concreto non è stato definito? AnyNode& operator=(const AnyNode& op)
da class AnyNode
verrà chiamato e nessuna azione verrà eseguita. Ma voglio sapere il AnyNode&
rvalue subito dopo l'iscrizione, in modo che sia possibile dedurne il tipo concreto con RTTI e prendere delle azioni sensate. L'operatore di pedici / assegnazioni potrebbe assomigliare a questo:
AnyNode& operator[]=(int i, const AnyNode& val)
{
//AnyNode::getType() is provided and it returns a value of the predefined enum.
//stor is an underlying container that holds pointers to AnyNode,
//assume it's possible to reference stor[i].
//NodeNumber is another subclass of AnyNode.
switch(val->getType())
{
case nodetypeNumber:
stor[i] = new NodeNumber();//Also we may need to delete stor[i] before new allocation.
//Process further specific initialization of stor[i].
break;
//................
}
//Or we could use dynamic_cast instead of switch(val->getType())
}
Suppongo che un tale tipo di operatore di sottoscrizione / assegnazione possa essere facilmente e coerentemente integrato nel linguaggio. Inoltre, delimita le parti di codice che vengono eseguite quando l'espressione subscript è un lvalue e quando è un valore rvalue: con esso il solito operatore di pedice dovrebbe essere chiamato solo quando l'abbonamento è rvalore. Ovviamente, se l'operatore proposto non è definito, ma è definita una operator[]
, il codice dovrebbe essere eseguito in un modo usuale.
Quindi le mie domande sono: quali insidie potrei mancarmi di questo operatore e perché non è incluso nella lingua? Può rompere la coerenza linguistica in qualsiasi modo o può far emergere qualsiasi ambiguità nella lingua?