Newsgroup: it.comp.lang.c++
Titolo: Rappresentazione di valori nulli

Ciao a tutti.
Da un po' di tempo mi sto ponendo il problema di come rappresentare
valori nulli, intesi come e' consuetudine nell'ambito dei database: per
ogni tipo di dati che puo' assumere un campo esiste un valore nullo.
Il confronto con un campo con valore nullo ha valore falso per qualsiasi
operatore relazionale (=,<>,>,<,ecc.) ed e' solo possibile testare con
successo se il campo e' effettivamente nullo.
In C++ l'unico analogo esistente e' il caso dei puntatori nulli. Su
questo ci si puo' basare ad esempio per definire la convenzione in cui
invece di passare ad una funzione un valore del tipo atteso si passa un
puntatore. Il passaggio di NULL rappresenta il valore nullo.
Se questo e' abbastanza accettabile per il passaggio di parametri a
funzioni l'utilizzo di una simile convenzione per membri dati introduce
problemi di gestione della memoria, efficenza, ecc.
L'idea che desidero sottoporvi e' la definizione di un oggetto funzione
template, che mascheri l'adozione di valori convenzionali (da
individuare) per rappresentare appunto i valori nulli:
#include <stdexcept>
#include <limits>
// Non esistendo una convenzione universale, il caso generale non puo'
che
// lanciare un'eccezione
template <typename T> class Null {
  public:
    operator T () { throw std::out_of_range("Null value"); }
};
// Il caso dei puntatori e' ovvio
template <typename T> class Null<T *> {
    public:
        operator T * ()   { return 0; }
};
// Per tipi specifici si adottare convenzioni specifiche, ad esempio
template <> class Null<int> {
    public:
        operator int ()   { return std::numeric_limits<int>::min(); }
};
//ho poi definito una funzione di utilita'
template <typename T> inline bool isNull(const T & t) { 
  return t == Null<T>(); 
}
L'utilizzo potrebbe essere:
#include <iostream>
int main(int argc, char* argv[]) {
  int b = Null<int>();
  if ( b == Null<int>() )
    std::cout << "b e' nullo\n";
  int * p = &b;
  if ( p == Null<int *>() )
    std::cout << "p e' nullo\n";
  if ( isNull(*p) )
    std::cout << "*p e' nullo\n";
}
Cosa ne pensate?
Ciao,
Nicola

> Cosa ne pensate?
Da un punto di vista pratico mi sembra un ottimo metodo. Di fatto mi e`
spesso capitato di ricorrere a soluzioni analoghe in modo brutale (cioè
senza generalizzare il problema ma risolvendolo di caso in caso).
Una soluzione alternativa (per evitare di utilizzare un valore /comunque/
valido per il tipo di variabile per determinare se è NULL oppure no) sarebbe
quella di scrivere un wrapper attorno ai tipi standard per implementare
questo meccanismo (magari senza l'uso di puntatori ma una volgarissima
variabile bool opportunamente gestita) ma questa sarebbe come giustamente
dici tu una soluzione molto pesante in tutti termini (implementazione,
occupazione di memoria, velocità di esecuzione, ecc.)
Quindi a mio avviso la soluzione che proponi e` molto valida e completa.

 [...]
Mi sembra interessante, ma resta il problema che il valore Null è
comunque un valore accettabile per T.
La cosa è ben diversa da un puntatore nullo.
Forse si dovrebbe scrivere una classe Handle per T.
Io l'ho fatto, è venuta abbastanza interessante ma è anche
abbastanza lunga (98 righe) ed evito di postarla
(a meno che non interessi a qualcuno).
Manlio Perillo

 La cosa è ben diversa da un puntatore nullo.
Non mi e' chiaro dove sia il problema.
Ciao,
Nicola

 ha scritto nel messaggio
news:3A06F0F1...@divalsim.it...

int main(int argc, char* argv[])
{
  int b = Null<int>();
  if( b == Null<int>() ) std::cout << "b e' nullo\n";
}
In questo banale esempio potrebbe benissimo accadere che
tu faccia
int b = x;
e che x sia proprio uguale a Null<int>();
In questo caso quando fai il test ottieni che b è nullo.
E' chiaro che Null<int>() può essere un valore che non
è mai usato, ma chi ci assicura?
Una soluzione migliore che risolve questo problema (ma che
ne aggiunge piccoli altri) è di creare un handle.
Alla fine è chiaro che l'approccio da seguire dipende dal
particolare problema.
Visto che mi trovo ti posto il codice che ho scritto, solo
per avere a confronto due possibili approcci:
struct Null_t {  };
const Null_t Null = { };
template<class T> class Handle
{
   public:
      // valore nullo per T, specializzabile
      static const T& null();
   private:
      T val;
      bool is_null;
   public:
      explicit Handle(const T& data)
      : val(data), is_null(false) {  }
      Handle(Null_t)
      : val(null()), is_null(true) {  }
      Handle& operator=(const T& data)
      {
         val = data;
         is_null = false;
         return *this;
      }
      Handle& operator=(Null_t)
      {
         val = null();
         is_null = true;
         return *this;
      }
      operator const T&() const { return val; }
      operator T&() { return val; }
      const T* operator&() const { return &val; }
      T* operator&() { return &val; }
      const T& operator*() const { return val; }
      T& operator*() { return val; }
      const T* operator->() const { return &val; }
      T* operator->() { return &val; }
      // operatori booleani
      friend bool operator==(const Handle& sx, Null_t)
      { return sx.is_null; }
      friend bool operator==(const Handle& sx, const Handle& dx)
      {
         if(sx.is_null || dx.is_null) return false;
         else return sx.val == dx.val;
      }
};
#include <stdexcept>
template<class T> const T& Handle<T>::null()
{
   throw std::out_of_range("Null value");
}
// operatori booleani
// ...
Dovrebbe apparire chiara l'idea.
Ora Handle<T>::null può anche essere un valore valido ed usato,
ma questo non ci interessa più.
Un'altro approccio è fare:
template<class T> class Handle: public T { // ... };
ma non è applicabile ai built-in.
Manlio Perillo

 Cosa ne pensate?
Con finalità e motivazioni, credo, analoghe alle tue io avevo escogitato
una soluzione molto simile, praticamente identica, direi, nello spirito e
un po' diversa nella forma (riporto un frammento del codice):
 template < class T >
 struct nullable_traits { };
 template <class T >
 struct nullable_traits<T*>
 {
   typedef T* null_type;
   static null_type null_value() { return 0; }
 };
 template <>
 struct nullable_traits<std::string>
 {
  typedef const char* null_type;
  static null_type null_value() { return "\1"; }
 };
 template < class T >
 typename nullable_traits<T>::null_type
  null()
  {
   return nullable_traits<T>::null_value();
  }
 int
 main()
 {
  char* ptr;
  if( ptr == null<char*>() ) { // ...
 
  std::string s;
  if( s == null<std::string>() )  { // ...
 }

 comunque un valore accettabile per T.
Si, pero' assumendo come nullo un valore all'estremo della validità di
esistenza di un tipo particolare e` praticamente irrisoria la limitazione
che si pone e si ha appunto il vantaggio di non dover gestire puntatori
dinamici appesantendo l'elaborazione.

 Non mi e' chiaro dove sia il problema.
Cioe', in realta' mi e' chiarissimo, solo che l'unica alternativa che
vedo e' l'adozione di una classe involucro che rappresenti in modo
esplicito il valore nullo, ad es.:
temlate <typename T> class Nullable {
  public:
    Nullable() : null(true) {}
    Nullable(const T & v) : val(v), null(false) {}
    operator T () { 
      if ( null ) throw std::out_of_bounds("valore nullo");
      return val;
    }
  private:
    T val;
    bool null;
};
In effetti la mia soluzione non risolve il problema di individuare una
convenzione universale per la rappresentazione del valore nullo, quanto
piuttosto un meccanismo per uniformare l'interfaccia a convenzioni
diverse.
Ciao,
Nicola

 };
Ciao,
Nicola


Scusa ma non ti seguo.
Manlio Perillo

 ogni tipo di dati che puo' assumere un campo esiste un valore nullo.
Secondo me questa "consuetudine" crea solo problemi. Per l'integrità di
un database l'esistenza di un valore nullo non è, secondo me, una cosa
positiva. Quando si crea un record si dovrebbe controllare
(esplicitamente nell'applicazione o implicitamente tramite i controlli
di integrità del DBMS) che ad ogni campo venga obbligatoriamente
assegnato un valore valido nel range di quelli accettati dal tipo del
campo stesso.
EMG

 
Ciao,
  ho letto i vari messaggi e i relativi sorgenti.
L'argomento è controverso e quindi prima di partire col codice provo a stendere una lista di requisiti da soddisfare. 
Spero possiamo concordare tutti sui i requisiti per poi cercare l'implementazione ottimale in C++.
Ecco una bozza:
1) Il valore "null" NON deve appartenere al dominio dei data-type nativi.
   Deve appartenere ad un domonio proprio "Tnull" che è disgiunto da ogni altro 
   dominio.
   note: non è possibile scegliere un valore del domonio di un data-type come 
         valore convenzionale di "null" in quanto tale scelta sarebbe dipendente
         dal singolo contesto applicatico.
2) Non vi sono operatori definiti sui valori di tipo "Tnull"
   note: vi sono definizione differenti delle operazioni sui valori "null".
         Per esempio l'SQL introduce il valore "UNKNOWN" mentre certi linguaggi
         definiscono ogni operazione con valori "null" ad esito "null".
         La soluzione che evita ogni confusione è quella di fornire la sola
         funzione di test IsNull.
3) Il nuovo tipo di dato "nullableC" dovrà poter assumere valori da un dominio C sia
   esso un data-type nativo o una classe definita dall'utente.
   Inoltre dovrà poter assumere il valore "null". Ma non entrambi contemporaneamente.
   note: quindi dovrà avere l'operatore di assegnazione dal dominio e da valori di 
         "Tnull" ossia "null".
         Il nuovo tipo di dato potrebbe essere implementato un template (nullable<C>), 
         oppure no.
4) Il nuovo tipo di dato "nullableC" avrà una funzione boolean isNull() quale unico metodo per 
   testare se assume il valore null.
   note: conseguenza del punto 2).
 
5) Quando un oggetto di tipo "nullableC" non è "null" (isNull() == false) deve essere 
   sostituibile sia come l-value che come r-value ad una variabile o valore del dominio C.
   note: l'obiettivo e quello di avere la notazione più semplice ed intuitiva possibile.
         Si pensi agli operatori + - * \ = =+ ... == != ... * &.
 
6) Quando un oggetto di tipo "nullableC" è "null" (isNull() == true) ogni tentativo di  
   usarlo come l-value o r-value del dominio C causa una eccezione.
   note: l'obiettivo e quello di evitare sorprese quando si è convinti a torto che il 
         valore non sia "null".
Concordate?
Sono realizzabili in C++ questi requisiti?
_______________________________________
 Luca Minudel   Consulente Informatico
 
luca.m...@nline.it
 Conegliano (TV)
 WWW    
http://www.nline.it/consulting

In linea di principio sono d'accordo con te, ma nella pratica mi sono
sempre imbattuto in situazioni in cui questa era solo una vana speranza,
specialmente quando i dati avevano un'origine informale, ad esempio
intervista a persone.
Ciao,
Nicola

          valore non sia "null".
Come l-value deve essere sempre sostituibile, per poter essere
assegnata.
 
Ciao,
Nicola

Ecco un mio tentativo di definizione:
REQUISITI DEI TIPI ANNULLABILI
Nicola Musatti, 8/11/2000
Nota: Questa proposta si basa sulle definizioni di Concetto, 
Raffinamento e Modello utilizzati nella documentazione 
dell'STL [1]
Un tipo e' Annullabile (Annullable) se e' possibile 
assegnare il valore nullo alle variabili del tipo e se gli 
oggetti del tipo sono confrontabili con il valore nullo.
Annullabile e' un raffinamento di Assignable e Equality
Comparable.
Siano:
T	un tipo annullabile
Null	il valore nullo
t	un oggetto di tipo T
Le seguenti sono espressioni valide:
T(Null)
T t(Null)
T t = Null
t = Null
t == Null
t != Null
Un tipo e' Esteso a Nullo (Null Extended) se ha associato un 
tipo base tale per cui il tipo Esteso a Nullo e' Annullabile 
ed ogni suo valore diverso da Null e' convertibile ad un 
valore del tipo base.
Esteso a Nullo e' un raffinamento di Annullabile
Siano:
T	un tipo esteso a nullo
B	il tipo base di T
t	un oggetto di tipo T
b	un oggetto di tipo B
Le seguenti sono espressioni sempre valide:
T(b)
T t(b)
T t = b
t = b
t == b
t != b
Le seguenti espressioni sono valide se e solo se t != Null
B(t)
B b(t)
B b = t
b = t
Nota: Potrebbe convenire l'aggiunta del confronto minore, 
per poter utilizzare i tipi Annullabili ed Estesi a Nullo
nei contenitori STL.
[1] Austern, M. "Generic Programming and the STL", 
Addison Wesley, 1999

Nella nota precedente Null non e' definito in modo particolare, ma e'
evidentemente un oggetto. Forse sarebbe meglio definire la classe
class Null {}
senza prevedere una costante di tipo Null.
Ciao,
Nicola

 ha scritto nel messaggio
news:01c04921$68a9c9c0$010000c4@sun...consulting
Il mio esempio soddifa questi requisiti?
Manlio Perillo

 ha scritto nel messaggio
news:3A09309F...@divalsim.it...

Non è l'approccio che ti avevo suggerito io?
Manlio Perillo


Beh, se, ad esempio, il campo Codice Fiscale non puoi riempirlo subito
per mancanza di informazioni basta che lo lasci in bianco (tutti spazi)
o vuoto (stringa nulla) a seconda del tipo di database, ma aggiungere un
valore in più (Null) è una cosa che secondo me crea più problemi di
quanti ne risolva (e penso che non ne risolva nessuno).
EMG

 
Dal punto di vista della notazione certamente sono più comodi gli operatori. Tuttavia le due maggiori convenzioni esistenti a riguardo sono tra loro in contrasto. La più corretta è certamente quella data dall'SQL ma richiede di introdurre anche il valore UNKNOWN oltre al NULL con un'aggiunta pesante dei costi di implementazione e di consapevolezza nell'utilizzo.
L'uso di isNull() è certamente più semplice ed intuitivo.
Ciao


 t == b
Questo è il punto su cui non concordo.
Ti faccio un esempio pratico:
B è long. 
T è long esteso a nullo per rappresentare l'importo residuo di un mutuo.
La logica applicativa (per esempio) prevede che tale importo è sempre >=0 e quindi come null si sceglie il valore convenzionale -1 (per mutuo non ancora attivo o definito).
Supponi ora che la variabile b di tipo long  contenga il debito di un conto corrente. Potrà avere anche valori negativi in caso di credito.
Sia t == Null, sia b == -1. 
Quando confronto t == b cosa ottengo? True?
Io personalmente preferisco esplicitare la cosa con questa notazione:
t.isNull() ? false : t == b;
o equivalentemente
if(t.isNull())
  ...
else if (t == b)
  ...


 
Ora verifico...
Il requisito 1)  si.
Il requisito 2)  no. Hai definito l'operatore di uguaglianza sul valore Null.
Il requisito 3)  si.
Il requisito 4)  no. La funzione IsNull() manca.
Il requisito 5)  ??. 
Il requisito 6)  no. Nell'operatore di conversione al tipo base torni val anche se is_null.
Le modifiche al tuo codice per avere l'aderenza al punto 2) e 4) sono minime.
Anche per il punto 6) basta solo aggiungere il test a is_null con eventuale eccezione.
Ottentere l'equivalenza notazionale del tipo nullable (quando non vale null) col tipo base è indispensabile perche la tecnica sia convenientemente applicabile. 
Questo è quanto prevede il requisito 5). Mi risulta più difficile imaginarne l'implementazione. 
E' molto complicato, bisogna conoscere _molto_ bene la risoluzione di overloading di operatori, conversioni, costruttori etc.
Sarebbe già un buon inizio elencare (sotto forma di codice eseguibile?) tutte le notazioni disponibili per un tipo base (per es. int?) con la speranza di essere esaustivi (espressioni, assegnazioni, conversioni, referenziamento, puntatori, cos'altro???).
Questo potrebbe essere usato come test di attinenza al requisito 5) per l'implementazione di un tipo nullable.


 costruttori etc.
La classe Handle č un l-value perchč vi si puň assegnare un T:
Handle<int> k(6);
k = 156;
Poi č anche un r-value:
int i = k;
Infine per accedere a T basta usare operator*() e &();
Handle<int> s(9);
if(*k == *s) ...
int* p = &k;
Manlio Perillo

   ...
Mi pare che in questo modo diventi estremamente difficile usare tipi
annullabili con le librerie standard.
Ciao,
Nicola

 L'uso di isNull() è certamente più semplice ed intuitivo.
Vedi la risposta al tuo altro post.
 
Ciao,
Nicola

 int* p = &k;
Non te l'ho scritto prima, ma te lo scrivo adesso: questo non mi piace
per niente. Secondo me Handle deve avere "value semantics" cioe' bisogna
poter scrivere:
Handle<int> i, j;
i = Null;
j = 2;
int k = 2;
assert( i != 3 );
assert( j == k ); 
Ciao,
Nicola

 effettivamente nullo.
Quindi si devono definire per Handle tutti gli operatori
relazionali (visto che stiamo assumendo che T è un tipo
su cui sono definiti gli operatori relazionali).
A questo punto non vedo problemi.
Resta solo da scegliere se Handle debba contenere T
oppure se debba derivare da T (cosa comoda ma non
utilizzabile per i builtin).
Una scelta ottimale(?) è di definire il template come
derivato da T e poi specializzare per i builtin.
Manlio Perillo

Molto probabilmente è una cosa nota, ma è meglio
ricordarla:
class Type
{
   private:
     int i;
   public:
      Type(int ii)
      : i(ii) {  }
      Type& operator++() { ++i; return *this; }
      int get() { return i; }
};
template<class T> class Handle: public T
{
   public:
      Handle(const T& val)
      : T(val) {  }
};
#include <iostream>
int main()
{
   Type t(5);
   std::cout << "t = " << t.get() << '\n';
   ++t;
   std::cout << "t = " << t.get() << '\n';
   Handle<Type> h = t;
   std::cout << "h = " << h.get() << '\n';
   ++h;
   std::cout << "h = " << h.get() << '\n';
}
Manlio Perillo

Anche questa molto probabilmente è una cosa nota:
template<class T> class Handle
{
   private:
      T x;
   public:
      Handle(const T& val)
      : x(val) {  }
      operator T&() { return x; }
};
#include <iostream>
int main()
{
   int i = 5;
   std::cout << "i = " << i << '\n';
   ++i;
   std::cout << "i = " << i << '\n';
   Handle<int> h = i;
   std::cout << "h = " << h << '\n';
   ++h;
   std::cout << "h = " << h << '\n';
   Handle<int> h2 = i + 10;
   std::cout << "h == h2: " << (h == h2) << '\n';
}
Nota:
Il BCC 5.5 fa un poco di storie con:
    std::cout << h;
dicendo di trovare ambiguità con
    std::cout << (char) e
    std::cout << (unsigned char).
Il DJGPP invece compila senza problemi.
Manlio Perillo

 Manlio Perillo
Ho preso una strada un po' diversa, ma la posto separatamente perche' e'
un po' lunga.
Ciao,
Nicola

Ipotesi di implementazione di una classe che implementa il concetto di
tipo esteso a nullo da me descritto qualche giorno fa.
// la classe che rappresenta il valore nullo
class Null {};
// mi servono perche' non posso specificare un valore di default
// per un argomento di template booleano (non so se e' standard
// o un baco di BCB4)
class Yes {};
class No {};
// Permette di scegliere tra l'implementazione basata su booleano
// esplicito e quella che utilizza un valore valido per 
// rappresentare il valore nullo
template <typename T> class NullTraits {
  public:
    typedef No specialized;
    static T value() { return T(); }
};
template <typename T> class NullTraits<T *> {
  public:
    typedef Yes specialized;
    static T * value() { return 0; }
};
// Isola gli aspetti che dipendono dalla rappresentazione. 
// La versione generale utilizza un valore booleano...
template <typename T, typename S> class Value {
  public:
    Value() {}
    Value(Null) : null(true) {}
    Value(const T & v) : val(v), null(false) {}
    void assign(const T & data) { val = data; null = false; }
    void assign(Null) { null = true; }
    bool isNull() const { return null; }
    T get() const { return val; }
    bool equals(const Value & v) const {
      return null == v.null && ( null || val == v.val );
    };
  private:
    T val;
    bool null;
};
// ...mentre quella specializzata utilizza il valore 
// convenzionale specificato tramite traits
template <typename T> class Value<T,Yes> {
    typedef NullTraits<T> traits_type;
  public:
    Value() {}
    Value(Null) : val(traits_type::value()) {}
    Value(const T & v) : val(v) {}
    void assign(const T & data) { val = data; }
    void assign(Null) { val = traits_type::value(); }
    bool isNull() const { return val == traits_type::value(); }
    T get() const { return val; }
    bool equals(const Value & v) const { return val == v.val; }
  private:
    T val;
};
// Il template vero e proprio
template <typename T> class Nil {
  public:
    typedef T base_type;
    typedef NullTraits<T> traits_type;
    Nil() {}
    Nil(Null n) : val(n) {}
    explicit Nil(const base_type & v) : val(v) {}
    Nil & operator = (const base_type & data) { 
      val.assign(data); return *this; 
    }
    Nil & operator = (Null n) { val.assign(n); return *this; }
    operator base_type () const {
      if ( val.isNull() ) throw std::out_of_range("Null value");
      return val.get();
    }
    friend bool operator == (const Nil & lhs, const Nil & rhs) {
      return lhs.val.equals(rhs.val);
    }
    friend bool operator == (const Nil & lhs, const base_type & rhs) {
      return lhs == Nil<base_type>(rhs);
    }
    friend bool operator == (const base_type & lhs, const Nil & rhs) {
      return Nil<base_type>(lhs) == rhs;
    }
    friend bool operator != (const Nil & lhs, const Nil & rhs) {
      return ! ( lhs == rhs );
    }
    friend bool operator != (const Nil & lhs, const base_type & rhs) {
      return lhs != Nil<base_type>(rhs);
    }
    friend bool operator != (const base_type & lhs, const Nil & rhs) {
      return Nil<base_type>(lhs) != rhs;
    }
  private:
    Value<base_type,traits_type::specialized> val;
};
A questo mancano gli operatori di conversione a reference, che avevo
dimenticato. Mi lasciano perplesso, ma probabilmente non se ne puo'
farne a meno.
Ciao,
Nicola


Anche in questo caso, come ho scritto anche alla fine,
basta definire opportunamente gli operatori relazionali per Handle.
Manlio Perillo

 > t == b
Questo punto porta a queste conseguenze (ti risparmio la dimostrazione):
i)   Null == b  è false
ii)  Null != b  è true 
iii) Null == Null è true
iv)  Null != Null è false
E quindi mi chiedo: che differenza c'è tra un Null così definito ed una semplice costante T TNull = <un valore qualunque di T>?
Quale aiuto pratico fornisce alla programmazione (notazione e  sul controllo a compile-time,...) l'introduzione del Null così definito?


 
Questa rappresentazione rende impossibile utilizzare le funzionalità STL che usano gli operatori di confronto (== e !=).
Pensa all'informazione che Null contiene: "valore sconosciuto o non applicabile"; quindi il confronto non è calcolabile.
Cioè è corretto non poter utilizzare tali funzioni della STL.
Altre funzionionalità della STL usano l'ordinamento con l'operatore < (o permettono di passare come parametro il criterio di ordinamento).
Le STL richiedono un ordinamento stretto e debole (che "visivamente" equivale ad un grafo orientato privo di cicli e non necessariamente connesso).
Pensa ancora all'informazione che Null contiene: "valore sconosciuto o non applicabile"; quindi  x < Null è false e  Null < x è false inoltre Null < Null è false (visivamente Null è un singolo nodo sconnesso dal grafo dagli altri elementi).
A me sembra che la generalità delle STL permette di utilizzare coerentemente col suo significato anche il valore Null.
HTH
_______________________________________
 Luca Minudel   Consulente Informatico
 
luca.m...@nline.it
 Conegliano (TV)
 WWW    
http://www.nline.it/consulting
P.S. Non credo ci siano funzionalità della STL che usano gli operatori == o != sui valori dei parametri del Template. Se ricordo bene, usano solo il concetto di equivalenza indotto dall'ordinamento stretto debole ( equiv(x,y) <=> !(cmp(x,y) || cmp(y,x)) ).

 
Perdonami se insisto, ma quello che dici non mi convince.
Pensa ad a tre variabili int:
int qta_ordinata; // quantità ordinata di un articolo
int num_copie;    // numero copie disponibili di un libro
int day_diff;     // distanza tra due date in giorni 
Il valore di Null in int per qta_ordinata potrebbe essere -1,0, numeric_limits<int>::min(), numeric_limits<int>::max() oppure un numero negativo a scelta o un numero positivo abbastanza grande.
Il valore di Null in int per num_copie *non* può essere 0 (a differenza di qta_ordinata), può essere un numero positivo abbastanza grande (abbastanza è lo stesso che per qta_ordinata?), -1, un qualunque numero negativo oppure numeric_limits<int>::min() o numeric_limits<int>::max().
Ma quale valore va bene per day_diff che può assumere ogni valore positivo o negativo?
numeric_limits<int>::min() o numeric_limits<int>::max() ?
Forse... ma chi mi avvisa quando eseguendo ++day_diff che valeva numeric_limits<int>::max()-1 lo trasformo inconsapevolmente a Null?
HTH


 
Anch'io avevo implementato questi operatori come te, ma poi gli ho messi alla prova con questo esempio:
----------------
class intRef
{
public:
	intRef(int& r):m_val(r) {}
	intRef& operator=(int i) { m_val=i; return *this; }
private:
	int& m_val;
};
Handle<int> s(100);
intRef iref(s);
s = Null;
iref = 999; // stò modificando l'int interno ad s ma s.is_null è true
-----------------
E' giusto che il iref = 999 non dia errore perchè s è Null. Infatti il codice di iref.operator= stà lavorando con int e quindi si aspetta di gestire solo eccezioni generate da int (se ce ne sono) essendo questo un tipo base non ereditabile.
Piuttosto dovrebbe essere più esplicito in Handle<int> che si stà "dando fuori" un riferimento o un puntatore. 
Quindi in Handle<int> esporrei esplicitamente solo un l-value (con l'operatore di conversione a int) mentre toglierei gli operatori operator*() e &() sostituendoli a due funzioni il cui nome ne chiarisca l'uso (e ne espliciti le conseguenze ossia la semantica).
Ti viene in mente qualche altra alternativa?


 Quale aiuto pratico fornisce alla programmazione (notazione e  sul controllo a compile-time,...) l'introduzione del Null così definito?
Che una volta scelto il modo piu' adatto per rappresentare il valore
nullo per ciascun tipo che serve, l'interfaccia resta consistente.
Ciao,
Nicola

 P.S. Non credo ci siano funzionalità della STL che usano gli operatori == o != sui valori dei parametri del Template. Se ricordo bene, usano solo il concetto di equivalenza indotto dall'ordinamento stretto debole ( equiv(x,y) <=> !(cmp(x,y) || cmp(y,x)) ).
Mmh, ho studiato ma non ho ripassato...
Ciao,
Nicola

Guarda il mio esempio...
Ciao,
Nicola

 poter accedere ai suoi metodi.
Se T non e' built-in e deve essere Annullabile, si spera che il suo
autore lo scriva come tale.
Ciao,
Nicola

 Ti viene in mente qualche altra alternativa?
Si puo' mantenere l'operatore &, facendo in modo che lanci un'eccezione
se l'oggetto e' nullo.
Ciao,
Nicola

 Ti viene in mente qualche altra alternativa?
Riguardo ad operator*() sono d'accordo che si può togliere.
Riguardo ad operator&() no.
Infatti definendo l'operatore di conversione in T, Handle<T> può
essere usato *esattamente* come un T (vedi la mia precisazione).
Tuttavia operator&() ha un significato predefinito per una classe,
per questo l'ho ridefinito.
In questo modo Handle<int> può, a tutti gli effetti, essere usato
come un T.
Per non avere problemi basta che tale operatore lanci una eccezione
nel caso in cui Handle sia Null.
Un'altro operatore che serve è ->().
Infatti è indispensabile per accedere agli (eventuali)
metodi di T nel caso non sia un built-in.
Come per gli altri operatori, questo è sotto il nostro
controllo.
Manlio Perillo


Per operatore di conversione a reference intendi:
    operator base_type&
?
Credo che mettere insieme questo e
    operator base_type
porti a delle ambiguità.
Manlio Perillo


Posto la mia implementazione rivista:
#include <stdexcept>
template<bool debugging, class A, class E>
inline void Assert(const A& assertion, const E& except)
{
   if(debugging)   // test fatto a compile-time
   {
      if(!assertion) throw except;
   }
}
struct Null_t {  };
const Null_t Null = { };
//
// Assume che T è un builtin oppure che abbia un costruttore
// di default e definiti gli operatori relazionali
//
template<class T> class Handle
{
   private:
      T val;
      bool is_null;
   public:
      explicit Handle(const T& data)
      : val(data), is_null(false) {  }
      Handle(Null_t)
      : is_null(true) { /* non inizializza esplicitamente val */ }
      Handle& operator=(const T& data)
      {
         val = data;
         is_null = false;
         return *this;
      }
      Handle& operator=(Null_t)
      {
         // non tocca val
         is_null = true;
         return *this;
      }
      operator T&() const
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return val;
      }
      operator const T&() const
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return val;
      }
      T* operator&()
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return &val;
      }
      const T* operator&() const
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return &val;
      }
      //
      // Necessario per accedere alle (eventuali) funzioni membro
      // di T, nel caso non sia un builtin
      //
      T* operator->()
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return &val;
      }
      const T* operator->() const
      {
         Assert<true>(!is_null, std::out_of_range("Null value"));
         return &val;
      }
      // operatori relazionali friend
      // ...
};
Come si nota, quando Handle diventa Null, l'attuale valore
di val non viene toccato, tanto non importa.
Se poi qualcuno ha accesso a val, ad esempio con:
Handle<int> k(45);
int& ref = k;
k = Null;
// k vale 45, ma non importa perchè in tale caso si può
// dire che il valore di k è undefined.
// Anche se k vale 0 non cambia niente.
// Resta il fatto che stiamo 'barando'.
Ossia una referenza ad Handle come ref viene 'invalidata'
quando Handle viene modificato
(come con gli iteratori nella STL).
Manlio Perillo

 Un'altro operatore che serve è ->().
Giusto, non ci avevo pensato.
Ora mi "eclisso" per qualche giorno per rivedere meglio i listati che avete postato tu e Nicola Musatti e per completare la versione a cui stò lavorando.
Quando la termino la posto con la speranza possiate aiutarmi a trovare errori di programmazione (ho ancora bisogno di fare esperienza sugli operatori in overload).
Spero di vedere anche i vostri listati definitivi.
Nel frattempo rilancio l'idea di produrre un programma di test con cui mettere alla prova il tipo Nullable, nella sua correttezza e sostituibilità trasparente ad un tipo base e non.


 in contro.
Forse ti stai confondendo, ma operator&() è usato per prendere
l'indirizzo di memoria di un oggetto:
Handle<int> k(56);
const int* p = &k;
Per questo l'ho definito.
Altrimenti &k restituisce un Handle<int>*.
Al limite per evitare problemi di 'intrusione' all'interno della
classe si può fare in modo che tutti questi operatori restituiscano
const T& e const T*:
operator const T&();
const T* operator->();
const T* operator&();
In questo modo dovremmo essere a posto.
Manlio Perillo

 porti a delle ambiguità.
Non ho provato, ma mi sa che hai ragione. Resto convinto che la mia
implementazione vada sostanzialmente bene cosi' com'e'.
Ciao,
Nicola

 controllo.
Non condivido.
Ciao,
Nicola


Il problema č che se fai:
Nil<int> k = Null;
if(k < 7);
viene usato l'operatore di conversione che a sua volta
lancia una eccezione.
Ti va bene cosě?
Inoltre:
Nil<int> k2 = Null;
if(k < k2)
lancia una eccezione, mentre sarebbe meglio restituisse false.
Questi operatori relazionali non vengono mai usati?
Manlio Perillo


Avrai i tuoi buoni motivi.
Manlio Perillo

 Questi operatori relazionali non vengono mai usati?
In linea di principio potrebbe anche andar bene, perche' effettivamente
e' arbitrario dire che un valore nullo e' minore (o maggiore) di un
altro valore. Il problema e' che se non si definisce operator < non e'
possibile usare molti algoritmi/contenitori, e questa mi sembra una
restrizione un po' pesante.
Ciao,
Nicola

 Avrai i tuoi buoni motivi.
Infatti. Se definisci operator -> stai di fatto implementando uno smart
pointer, nel qual caso la gestione del valore nullo richiede
considerazioni che esulano dagli aspetti che, secondo me, stiamo
discutendo in questo thread.
Ciao,
Nicola


Ma in questo modo se il tipo base ha delle funzioni membro, non
c'č un modo diretto per accedervi.
Derivare Nil dal tipo base non č possibile, in quanto le conversioni
da Nil nel tipo base non sono piů sotto il nostro controllo.
Manlio Perillo

 da Nil nel tipo base non sono piů sotto il nostro controllo.
Hai ragione. Cosi' com'e' Nil e' solo adatto a estendere tipi numerici
nativi e non classi o puntatori. In effetti aggiungere l'operatore ->
non e' un grosso problema, poiche' ha senso solo se il tipo base e'
effettivamente dereferenziabile, e i suoi abusi sono piuttosto
convoluti. Per le classi andrebbe utilizzata la stessa sintassi,
trasformando di fatto Nil in uno smart pointer.
Nota che anche nella forma limitata attuale Nil risponde ad un'esigenza
concreta: poni di dover calcolare un valore che sara' utilizzato solo in
seguito e che la funzione di calcolo possa fallire (ad es. perche'
utilizza un metodo iterativo che in condizioni particolari puo' non
convergere). In questo caso le eccezioni non sono molto utili, perche'
bisogna conservare memoria del fallimento del calcolo per permettere la
scelta di strategie alternative al momento dell'utilizzo. Una situazione
analoga si ha leggendo un campo nullo da un database in una
rappresentazione intermedia che sara' utilizzata in seguito.  
In questi casi' Nil consente una rappresentazione uniforme, anche se il
risultato "valido" potra' essere utilizzato solo tramite operatori che
non sono necessariamente membri, o per conversione esplicita.
Andare oltre, come abbiamo visto, comporta vantaggi e svantaggi. Il mio
punto di vista e' che per quanto riguarda i puntatori e' abbastanza
inutile, visto che la loro semantica gia' prevede un valore nullo
convenzionale. Per le classi user defined la semantica di Annullabile
andrebbe codificata direttamente. Certamente questo non consente di
rendere Annullabili classi di libreria che non lo siano gia'.
Ciao,
Nicola


Forse vale la pena sottolineare che se usi un operator T()
(non usando cioč una referenza), non č possibile fare, ad esempio:
Nil<int> k(3);
++k;
Come hai scritto anche tu, ogni soluzione ha i suoi vantaggi
e svantaggi.
L'importante č che uno conosca tutte le alternative che ha a
disposizione (e le loro conseguenze), in modo da poter fare
una scelta 'consapevole'.
Manlio Perillo

 una scelta 'consapevole'.
D'accordo. In effetti la situazione che ho descritto rappresenta un caso
che ho davvero incontrato, e nel quale gia' Nil avrebbe potuto
costituire una soluzione valida.
Ho anche speso qualche secondo a pensare se sia possibile implementare i
requisiti di Annullabile tramite una classe base, ma come succede per i
concetti SQL, non penso esista una soluzione accettabile in termini
puramente Object Oriented.
Ciao,
Nicola

In questi giorni ho trovato qualche ora per visionare il codice inviato al newsgroup (da Manilo Perillo e Nicola Musatti) e poi per completare l'implementazione dei miei requisiti.
Durante l'implementazione ho chiarito due aspetti che mi interessavano e che costituiscono le maggiori differenze ai requisiti espressi da Manilo Perillo e Nicola Musatti:
i) Per ottenere la sostituibilità è necessario conoscere le caratteristiche delle classi che si vorrà rendere Nullable; quindi ho implementato il template TNullable<T> per i build-in type del C++. Gli user-defined type possono implemetare da soli la semantica Nullable oppure è possibile applicargli Adapter specifici mantenendo la sostituibilità.
ìì) L'SQL ha necessità di prevedere gli operatori per il valore NULL in quanto è un linguaggio dichiarativo e quindi è indispensabile poter valutare espressioni che contengano valori a NULL. Questa complessità elevata può essere evitata nei linguaggi procedurali.
Sarei felice se mi indicaste eventuali ingenuità, errori o mancanze sul codice C++.
Invio il codice nei due messaggi successivi:
tnullable.h - contiene il template che permette di rendere nullable un buld-in type
main.cpp    - per testare il template e verificare la sostituibilità
Ciao.


// TNullable.h: Namespace nsNullable.
//
//////////////////////////////////////////////////////////////////////
/*
	Requisiti
	------------  
	1) Il valore "Null()" NON deve appartenere al dominio del data-type nativo.
	   Deve appartenere ad un dominio proprio "class Tnull" che è disgiunto da ogni altro 
	   dominio.
	   note: non è possibile scegliere un valore del domonio di un data-type come 
			 valore convenzionale di "Null()" in quanto tale scelta sarebbe dipendente
			 dal singolo contesto applicatico.
	2) Non vi sono operatori definiti sui valori di tipo Tnull
	   note: In SQL le gli operatori definiti per NULL sono necessari in quanto è un 
	         linguaggio dichiarativo e quindi richiede di valutare espressioni contenenti
			 il NULL. La complessità (e facilità d'errore) introdotta da operatori definiti
			 per il NULL può essere evitata in un linguaggio procedurale.
	3) Il nuovo tipo di dato "TNullable<T>" avrà una funzione boolean isNull() quale unico 
	   metodo per testare se assume il valore null.
 
	5) Quando un oggetto di tipo "TNullable<T>" non è "null" (isNull() == false) deve essere 
	   sostituibile sia come l-value che come r-value ad una variabile o valore del dominio C.
	   note: l'obiettivo e quello di avere la notazione più semplice ed intuitiva possibile.
			 Si pensi agli operatori + - * \ = =+ ... == != ... * &.
 
	3) Il nuovo tipo di dato "TNullable<T>" dovrà poter assumere valori da un dominio T di
	   build-in type di C++. Inoltre dovrà poter assumere il valore "Null()". 
	   Ma non entrambi contemporaneamente.
	   note: tipi definiti dall'utente possono essi stessi implementare la semantica "Nullable"
	         oppure una classe Adapter può aggiungere tale semantica.
			 La conoscenza del tipo che si va a rendere Nullable è necessaria per implementare
			 la sostituibilità
  6) Quando un oggetto di tipo "TNullable<T>" è "Null()" (isNull() == true) ogni tentativo di  
	   usarlo come l-value o r-value del dominio C causa una eccezione.
	   note: l'obiettivo e quello di evitare sorprese quando si è convinti a torto che il 
			 valore non sia "Null()".
*/
#ifndef AFX_NSNULLABLE_H
#define AFX_NSNULLABLE_INT_H
#include<iostream>
#include<stdexcept>
namespace nsNullable{
/////////////////////////////////////////////////////////////////////////////////////////
//  Null() symbol
/////////////////////////////////////////////////////////////////////////////////////////
	class TNull{
	friend TNull Null();
	private:
		TNull(){}
		TNull(const TNull&);
		TNull& operator=(const TNull&);
	};
	TNull Null(){ return TNull(); }
 
/////////////////////////////////////////////////////////////////////////////////////////
//  TNullable Template
/////////////////////////////////////////////////////////////////////////////////////////
	template<class T> class TNullable
	{
	public:
		/////////////////////////////////////////////////////////////////////////////////
		// Generic Typef of templates
		/////////////////////////////////////////////////////////////////////////////////
		typedef T	 value_type;
		typedef long size_type;
		typedef long difference_type;
		typedef T*         pointer;
		typedef const T*   const_pointer;
		typedef T&         reference;
		typedef const T&   const_reference;
		/////////////////////////////////////////////////////////////////////////////////
		// Foundation members 
		/////////////////////////////////////////////////////////////////////////////////
		// default constructor
		TNullable<T>():m_isNull(true){}
		// constructor
		TNullable<T>(const TNull&):m_isNull(true){}
		TNullable<T>(const int& ii):m_value(ii),m_isNull(false){}
		// copy-constructor
		TNullable<T>(const TNullable<T>& nn):m_isNull(nn.m_isNull)
		{ if (!nn.m_isNull) m_value = nn.m_value; }
		/////////////////////////////////////////////////////////////////////////////////
		// Inspectors members
		/////////////////////////////////////////////////////////////////////////////////
		bool isNull() const { return m_isNull; } 
		/////////////////////////////////////////////////////////////////////////////////
		// Modifiers operators
		/////////////////////////////////////////////////////////////////////////////////
		// copy-assignment operator
		TNullable<T>& operator=(const TNullable<T>& nn) 
		{ if(!(m_isNull=nn.m_isNull)) m_value=nn.m_value; return *this; }
		// assignment operator
		TNullable<T>& operator=(const TNull&) { m_isNull=true; return *this; }
		// int assignment operators
		TNullable<T>& operator=(const int& ii) { m_isNull=false; m_value= ii; return *this; }
		TNullable<T>& operator+=(const int& ii) { checkNotNull(); m_value+=ii; return *this; }
		TNullable<T>& operator-=(const int& ii) { checkNotNull(); m_value-=ii; return *this; }
		TNullable<T>& operator*=(const int& ii) { checkNotNull(); m_value*=ii; return *this; }
		TNullable<T>& operator/=(const int& ii) { checkNotNull(); m_value/=ii; return *this; }
		TNullable<T>& operator%=(const int& ii) { checkNotNull(); m_value%=ii; return *this; }
		TNullable<T>& operator^=(const int& ii) { checkNotNull(); m_value^=ii; return *this; }
		TNullable<T>& operator&=(const int& ii) { checkNotNull(); m_value&=ii; return *this; }
		TNullable<T>& operator|=(const int& ii) { checkNotNull(); m_value|=ii; return *this; }
		TNullable<T>& operator>>=(const int& ii) { checkNotNull(); m_value<<=ii; return *this; }
		TNullable<T>& operator<<=(const int& ii) { checkNotNull(); m_value>>=ii; return *this; }
		// Pre/Post increment/decrement operators
		TNullable<T>& operator++() { checkNotNull(); ++m_value; return *this; }
		TNullable<T>& operator--() { checkNotNull(); --m_value; return *this; }
		TNullable<T> operator++(int) 
		{ checkNotNull(); TNullable ni = *this; ++m_value; return ni; }
		TNullable<T> operator--(int) 
		{ checkNotNull(); TNullable ni = *this; --m_value; return ni; }
 
		// Dà fuori un riferimento. Operazione esplicita, effetti collaterali a carico 
		// dell'utilizzatore.
		T& getRef() { checkNotNull(); return m_value; }  
		const T& getRef() const { checkNotNull(); return m_value; }  
		// Dà fuori un puntatore. Operazione esplicita, effetti collaterali a carico 
		// dell'utilizzatore.
		T* getPtr() { checkNotNull(); return &m_value; }  
		const T* getPtr() const { checkNotNull(); return &m_value; }  
		/////////////////////////////////////////////////////////////////////////////////
		// Conversion operators
		/////////////////////////////////////////////////////////////////////////////////
		operator T() const { checkNotNull(); return m_value;}
	private:
		// Data members
		T m_value;
		bool m_isNull;
		// Private Functions
		void checkNotNull()  const
		{ if (m_isNull) throw std::range_error("Valore Null non consentito."); }
	};
	/////////////////////////////////////////////////////////////////////////////////////
	//  Helper Functions
	/////////////////////////////////////////////////////////////////////////////////////
	template<class T> bool isNull(const TNullable<T>& nn) { return nn.isNull(); }
	bool isNull(const TNull&) {	return true; }
	template<class T> std::ostream& operator<<(std::ostream& oo, const TNullable<T>& nn)
	{
		if(nn.isNull())
			oo << "<Null>";
		else
		{
			int i = nn;
			oo << i;
		}
		return oo;
	}
	/////////////////////////////////////////////////////////////////////////////////////
	//  Sorting Criteria
	/////////////////////////////////////////////////////////////////////////////////////	
	// Ordinamento stretto debole:
	// i) cmp(x,x) è false                   - antiriflessivo 
	// ii) cmp(x,y) && cmp(y,z) => cmp(x,z)  - transitivo 
	// i) + ii) cmp(x,y) => !cmp(y,x)        - antisimmetrico
	// iii) equiv(x,y) = !(cmp(x,y) || cmp(y,x)). equiv(x,y) && equiv(y,z) => equiv(x,z)
	template<class T> struct cmpNullMax{ // Null() è maggiore di ogni intero
		bool operator() (const TNullable<T>& x,const TNullable<T>& y) const // x < y
		{ return x.isNull() ? false : (y.isNull() || x<y); }
	};
	template<class T> struct cmpNullMin{ // Null() è minore di ogni intero
		bool operator() (const TNullable<T>& x, const TNullable<T>& y) const // x < y
		{ return y.isNull() ? false : (x.isNull() || x<y); }
	};
	template<class T> struct cmpNullEquiv{ // Null() è equivalente ad ogni intero
		bool operator() (const TNullable<T>& x, const TNullable<T>& y) const // x < y
		{ return !x.isNull() && !y.isNull() && x<y; }
	};
}
#endif // ifndef AFX_NSNULLABLE_H

#include "tnullable.h"
#include<iostream>
#include<vector>
#include<list>
#include<deque>
#include<cmath>
#include<algorithm>
using namespace nsNullable;
typedef nsNullable::TNullable<int> TNullable_int;
typedef nsNullable::cmpNullMax<int> cmpNullMax_int;
typedef nsNullable::cmpNullMin<int> cmpNullMin_int;
typedef nsNullable::cmpNullEquiv<int> cmpNullEquiv_int;
void TestNullSymbol();
void TestConstructors();
void TestRValue();
void TestLValue();
void TestLValueOperators();
void TestSTL();
int main(){
	TestNullSymbol();
	TestConstructors();
	TestRValue();
	TestLValue();
	TestLValueOperators();
	TestSTL();
 
	return 0;
}
void TestNullSymbol()
{
	// Null() è l'unico modo di esprimere il valore "null" di tipo TNull.
	// Non può essere creato, copiato, assegnato.
	// Deve compilare correttamente
	Null(); 
 
	// Non devono compilare
	//TNull();
	//TNull x;
	//TNull y = Null();
	//TNull z(Null());
	//Null()==Null();
}
void TestConstructors()
{
	// Test inizializzazione implicita a null
	TNullable_int();
	const TNullable_int();
	TNullable_int x;
	const TNullable_int cx;
	TNullable_int xx = Null();
	const TNullable_int cxx = Null();
	TNullable_int w = 10; // serve dopo
	// Test copy-constructor
	TNullable_int y = w; // da value = 10
	TNullable_int z = x; // da Null()
	z = x; // assignment
	y = Null(); // assignment
	const TNullable_int cw = 10;
	const TNullable_int cy = w;
	const TNullable_int cz = x;
	// Test inizializzazione esplicita a tipo convertibile implicitamente ad int
	TNullable_int(10);
	TNullable_int('c');
	TNullable_int(true);
	TNullable_int(10.10);
	TNullable_int(999999.9999);
	TNullable_int(int(-10));
	TNullable_int(unsigned int(10));
	TNullable_int(long int(-10));
	TNullable_int(unsigned long int(10));
	TNullable_int(short int(-10));
	TNullable_int(unsigned short int(10));
	TNullable_int(char('c'));
	TNullable_int(unsigned char('c'));
	TNullable_int(bool(true));
	TNullable_int(float(10.10));
	TNullable_int(double(10.10));
	TNullable_int(long double(-10.10));
}
void PassInt(int i) { i; return; }
void PassConstInt(const int i) { i; return; }
int ReturnInt() { TNullable_int ni = 100; return ni; } 
const int ReturnConstInt() { TNullable_int ni = 100; return ni; } 
TNullable_int ReturnTNullable_int() { TNullable_int nn=10; return nn; }
const TNullable_int ReturnConstTNullable_int() { TNullable_int nn=10; return nn; };
void TestRValue()
{
	// Verifivo la sostituibilità di TNullable_int ni = 10 a int 
	TNullable_int ni = 10; 
	int i = ni;
	const int ci = ni;
	int j = 10 + 20 * 30 / ni -1;
	const int cj = 10 + 20 * 30 / ni -1;
	i += ni;
	i *= ni;
	i /= ni;
	PassInt(ni);
	PassConstInt(ni);
	// Verifivo la sostituibilità di const TNullable_int cni = 20 a int 
	const TNullable_int cni = 20; 
	int ii = cni;
	const int cii = cni;
	int jj = 10 + 20 * 30 / cni -1;
	const int cjj = 10 + 20 * 30 / cni -1;
	ii += cni;
	ii *= cni;
	ii /= cni;
	PassInt(cni);
	PassConstInt(cni);
	// Verifivo la sostituibilità di TNullable_int come parametro di ritorno int
	int k = ReturnInt();
	const int ck = ReturnConstInt();
	// Verifivo la sostituibilità di TNullable_int come parametro di ritorno
	k = ReturnTNullable_int();
	const int ckk = ReturnConstTNullable_int();
	// Verifivo la non sostituibilità di TNullable_int quando vale Null();
	try{
		TNullable_int nnn = Null();
		int iii = nnn;
	}
	catch(std::range_error&){
		// Errore, non è possibile assegnare Null() ad un intero!
		int iii = -666;
	}
}
void PassRef2Int(int& i) { i; return;}
void PassPtr2Int(int* pi) { *pi; return; }
void PassConstRef2Int(const int& ci) { ci; return;}
void PassConstPtr2Int(const int* pci) { *pci; return; }
void TestLValue()
{
	// LValue modificabile
	TNullable_int ni = 100;
	ni = 10 * 20;
	ni = 10e+1 / 3;
	ni = 'c';
	int& ri = ni.getRef();
	const int& cri = ni;
	const int* cpi = ni.getPtr();
	PassRef2Int(ni.getRef());
	PassPtr2Int(ni.getPtr());
	PassConstRef2Int(ni.getRef());
	PassConstPtr2Int(ni.getPtr());
	// LValue costante
	const TNullable_int cni = 200;
	// Non compila perchè cni costante
	//int& ri2 = cni.getRef();
	const int& cri2 = cni;
	const int* cpi2 = cni.getPtr();
	// Non compila perchè cni costante
	//PassRef2Int(cni.getRef());
	//PassPtr2Int(cni.getPtr());
	PassConstRef2Int(cni.getRef());
	PassConstPtr2Int(cni.getPtr());
	// Esempio di effetto collaterale
	TNullable_int ni2 = 200;
	int& ri2 = ni2.getRef();
	ni2 = Null();
	ri2 += 100; // questo valore non è accessibile da ni2 che è Null();
}
void TestLValueOperators()
{
	TNullable_int ni;
	// Verifivo la non sostituibilità di TNullable_int quando vale Null();
	try{
		ni += 100;
	}
	catch(std::range_error&){
		// Errore, non è possibile sommare un intero a Null() !
		ni = 100;
	}
	ni += 20;
	ni -= 30;
	ni *= 40;
	ni /= 50;
	ni %= 20;
	ni ^= 20;
	ni &= -3;
	ni |= 2;
	ni >>= 3;
	ni <<= 3;
	ni++;
	++ni;
	ni--;
	--ni;	
}
void insertIt(std::vector<TNullable_int>& v, std::vector<TNullable_int>& w,
			  std::vector<TNullable_int>& vv, std::list<TNullable_int>& l,
			  std::deque<TNullable_int>& d, TNullable_int ni)
{
		v.push_back(ni);
		w.push_back(ni);
		vv.push_back(ni);
		l.push_back(ni);
		d.push_back(ni);
}
void print(const TNullable_int& ni)
{
	std::cout << ni << " ";
}
void TestSTL()
{
	std::vector<TNullable_int> v1,v2,v3;
	std::list<TNullable_int> l1;
	std::deque<TNullable_int> d1;
	for (int i=0; i<20; ++i)
	{
		if (i%3)
			insertIt(v1,v2,v3,l1,d1,TNullable_int(10.0 * sin((6.28 * i)/20)));
		else
			insertIt(v1,v2,v3,l1,d1,TNullable_int(Null()));
	}
	std::sort(v1.begin(), v1.end(), cmpNullMax_int());
	std::sort(v2.begin(), v2.end(), cmpNullMin_int());
	// NB l'algoritmo di sort utilizzato non rispetta il concetto di 
	//    ordinamento stretto debole: if, for each N in the range [0, last - first) and for 
	//    each M in the range (N, last - first) the predicate 
	//    !cmpNullEquiv(*(first + M) , *(first + N)) is true. 
	//    E' un errore della STL? E' una scelta per le prestazioni?
	std::sort(v3.begin(), v3.end(), cmpNullEquiv_int());
	std::for_each(v1.begin(), v1.end(), &print);
	std::cout << std::endl;
	std::for_each(v2.begin(), v2.end(), &print);
	std::cout << std::endl;
	std::for_each(v3.begin(), v3.end(), &print);
	std::cout << std::endl;
	std::cout << std::endl;
	std::sort(d1.begin(), d1.end(), cmpNullMax_int());
	std::for_each(d1.begin(), d1.end(), &print);
	std::cout << std::endl;
	std::cout << std::endl;
}

 al newsgroup (da Manilo Perillo e Nicola Musatti)
Ehm, mi chiamo Manlio non Manilo.
Pensavo che il mio nome potesse essere frainteso solo
se sentito a 'voce' ma mi sbagliavo....
Manlio Perillo

 
