Newsgroup: it.comp.lang.c++
Titolo: conversione e funzioni membro

Saluti.
Ho provato il seguente codice con il VCC, BCC e GPP:
#include <iostream>
struct base
{
};
class test
{
    public:
    void f() { std::cout << "test::f\n"; }
};
void foo(base* obj, void (base::*mf)())
{
    (obj->*mf)();
}
int main()
{
    test t;
    foo(reinterpret_cast<base*>(&t), reinterpret_cast<void
(base::*)()>(&test::f));
    std::cin.get();
}
ed ottengo sempre:
test::f
Mi chiedevo se tale comportamento è Standard o meno.
Grazie  Manlio Perillo

 test::f
Compilato con il Comeau non fa una grinza,del resto non vedo motivi per cui
tale codice non debba poter essere compilato.
L'esempio seguente e' qualcosa di altrettanto insensato ma perfettamente
legale e funzionante:
#include<iostream>
using namespace std;
class foo {
  public:
    void print(){ cout << "sono <foo::print()>" << endl; }
};
int main()
{
 foo* nullFoo = 0;
 nullFoo->print();
 return 0;
}

"Manlio Perillo" ha scritto
: Saluti.
:
: Ho provato il seguente codice con il VCC, BCC e GPP:
:
:
: #include <iostream>
:
: struct base
: {
: };
:
: class test
: {
:     public:
:     void f() { std::cout << "test::f\n"; }
: };
:
:
: void foo(base* obj, void (base::*mf)())
: {
:     (obj->*mf)();
: }
:
: int main()
: {
:     test t;
:     foo(reinterpret_cast<base*>(&t), reinterpret_cast<void
: (base::*)()>(&test::f));
:     std::cin.get();
: }
Ci avevo giocato anch'io.
reinterpret_cast tra puntatori a funzioni membro di tipo diverso e`
unspecified behaviour a meno che non venga applicato due volte, cosi`
da riportare al tipo di puntatore originale.
Dal Draft pre votazione:
5.2.10 - Reinterpret cast [expr.reinterpret.cast]
...
9- An rvalue of type ``pointer to member of X of type T1'' can be explicitly
converted to an rvalue of type ``pointer to member of Y of type T2'' if T1
and T2 are both function types or both object types.*
[Footnote: T1 and T2 may have different cv-qualifiers, subject to the
overall restriction that a reinterpret_cast cannot cast away constness.
--- end foonote]
The null member pointer value (conv.mem) is converted to the null member
pointer value of the destination type. The result of this conversion is
unspecified, except in the following cases:
^^^^^^^^^^^^
converting an rvalue of type ``pointer to member function'' to a different
pointer to member function type and back to its original type yields the
original pointer to member value.
converting an rvalue of type ``pointer to data member of X of type T1'' to
the type ``pointer to data member of Y of type T2'' (where the alignment
requirements of T2 are no stricter than those of T1) and back to its
original type yields the original pointer to member value.
ciao
Massimo

 }
Dereferenziare un puntatore nullo e' comportamento indefinito. Dopodiche
la soluzione piu' comoda e' trasformare foo::print() in 
void print_foo(foo * const);
Dal momento che il parametro non viene utilizzato in genere tutto va
bene.
Ciao,
Nicola

 bene.
non dovrebbe emettere almeno un warning? certo non e' possibile 
verificare sempre se si dereferenzia un puntatore nullo, ad esempio se 
una f. lo ritorna, ma in questo caso e' esplicito

 [CUT]
Quindi è solo unspecified behaviour e non undefined behaviour.
Questo significa che la cosa non è specificata dallo Standard ma
lo è per un compilatore?
Ossia per i compilatori su citati l'approccio è valido?
(Nota: non intendo usare il metodo con funzioni virtuali)
Se è così resta solo da vedere se il comportamento è stabile
rispetto alle nuove versioni.
Il mio problema è che devo fare un wrapper per una libreria scritta in C
e l'approccio tramite funzioni virtuali è poco flessibile.
Ciao e grazie   Manlio Perillo

"Manlio Perillo" ha scritto
:
: Quindi è solo unspecified behaviour e non undefined behaviour.
: Questo significa che la cosa non è specificata dallo Standard ma
: lo è per un compilatore?
Bisogna vedere se il compilatore documenta il comportamento
non specificato dallo standard. Se non lo documenta, e` libero
si cambiarlo da una versione alla successiva senza bisogno di
renderlo noto. E` un dettaglio implementativo. Mi pare che
g++ fornisse un modo per trasformare una funzione membro in una
funzione che accetta come primo parametro il puntatore all'oggetto.
Il borland fornisce le closure. Penso che si debba quantomeno seguire
la sintassi delle estensioni al linguaggio come sono specificate dalla
documentazione del compilatore, ma di questo non so dirti di piu`.
ciao
Massimo

 wrote in message
news:1758040.L...@news.iol.it...

Nessun warning è richiesto. Il comportamento è indefinito. Il
compilatore puù fare quello che vuole e il tuo programma, quando esegue,
può fare anche peggio.
--
Andrea Ferro
IHHC - Member of HT-Group  DVD Authoring operator  Fan of I.Asimov
C++ Master: 
http://www.brainbench.com/transcript.jsp?pid=2522556
This post expresses personal opinions which may or may not be the
opinions of the company(s) I work for. I speak for myself only.

 Dereferenziare un puntatore nullo e' comportamento indefinito.
cosa si intende di preciso con "dereferenziazione"?
Ad esempio le codice precedente sappiamo benissimo che non avremmo mai 
alcun accesso alla memoria puntata da nullFoo (infatti il compilatore si 
limita a copiare il suo valore in this, passarlo a print la quale non lo 
tocca; inoltre foo non e` polimorfica, quindi non cerca neppure di 
accedere alla virtual table); oppure anche la semplice applicazione di 
operator-> e` considerata dereferenziazione?
ciao Anto
-- 
Antonio Cuni, 19 anni, studente
Brainbench C++ Master: 4.73, higher than 99% of all previous test takers.
C Language: 3.79, higher than 92% of all previous test takers.
Guida C/C++ di Programmazione.it: 
http://cpp.programmazione.it

 [CUT]
Sono andato a ripescare del codice che aveva scritto Max M. in una discussione
avuta con te sui delegate.
Il codice era:
template<class T, class Result, class Arg, Result(T::*mf)(Arg)>
inline Result call_memfunc(void* p, Arg a)
{
    return (static_cast<T*>(p)->*mf)(a); // Max aveva usato reinterpret_cast ma
non ne vedo il bisogno
}
template<class Result, class Arg>
class delegate
{
    private:
        typedef Result(*func_t)(void*, Arg);
        void*  obj_;
        func_t func_;
    public:
        template<class T, Result(T::*mf)(Arg)>
        void bind(T* p)
        {
            obj_ = p;
            func_ = &call_memfunc<T, Result, Arg, mf>;
        }
        Result operator()(Arg a) { return func_( obj_, a); }
};
L'idea è veramente interessante.
Tuttavia Max scrisse:
"Mi pare comunque che abbia prestazioni peggiori rispetto a quella di Massimo:
 una chiamata a funzione in più."
Riporto anche il codice che avevi postato(essenzialmente lo stesso approccio che
avevo usato io):
template<typename Result, typename Arg>
class delegate
{
    private:
        typedef Result (delegate2::*func_t)(Arg);
        func_t     func_;
        delegate*  obj_;
    public:
        template<class T>
        void bind(T* ptr, Result (T::*mf)(Arg))
        {
         func_ = reinterpret_cast<func_t>(mf);
         obj_  = reinterpret_cast<delegate2*>(ptr);
        }
        Result operator()(Arg a) { return (obj_->*func_)(a); }
};
Non riesco a vedere dove è la chiamata a funzione in più.
Siccome mi serve usare i delegate, vorrei approfondire l'argomento:
La soluzione di Max è Standard?
E' suscettibile di ottimizazioni?
Il nuovo Standard per il C++ supporterà i delegate?
Saluti e grazie   Manlio Perillo

 
Boh.
Ciň che idealmente si dovrebbe ottimizzare, ma che allo stato attuale del
linguaggio non č possibile, č la risoluzione dinamica della chiamata nel
caso di funzioni virtuali. In una vera implementazione dei delegates, non
ha senso che a ogni chiamata venga nuovamente consultata la vtable.
A proposito di delegates/closures e C++0x, ho visto che ci sono state
discussioni a questo proposito su comp.std.c++ l'anno scorso, ma non le ho
seguite. Dovresti poterle recuperare con Google.
Max

"Manlio Perillo" ha scritto
:
: Sono andato a ripescare del codice che aveva scritto Max M. in una
discussione
: avuta con te sui delegate.
: Il codice era:
:
...
:
:
: Non riesco a vedere dove è la chiamata a funzione in più.
:
: Siccome mi serve usare i delegate, vorrei approfondire l'argomento:
:
: La soluzione di Max è Standard?
: E' suscettibile di ottimizazioni?
:
: Il nuovo Standard per il C++ supporterà i delegate?
:
Il problema, per quanto ricordo, delle versioni template e` che per ogni
combinazione di oggetto, tipo di funzione membro (lista di parametri e tipo
restituito) viene generato un nuovo tipo di delegate. Per cui se il tutto
non e` inline si rischia di avere molte copie di codice inutile, nulla piu`
che reinterpret_cast e chiamata di funzione. La mia versione era sicuramente
unspecified behaviour, Max ne aveva postate piu` di una, pero` il punto
e` che se esegui il cast anche al momento della chiamata il tipo della
funzione deve essere noto at compile-time, per cui dovresti finire per avere
piu` copie dello stesso codice.
Dato il tuo problema, che a quanto ho capito non prevede chiamate di
funzioni virtuali, mi sembra che esista una soluzione molto piu` semplice
e abbastanza rodata:
trasformi in static function o friend function che accettano come primo
parametro un puntatore a void, le funzioni membro che devono essere passate
e quindi le chiami senza piu` reinterpret_cast. La forzatura del type-system
e` la stessa cui si e` costretti quando si usano API che si aspettano delle
callback.
Qualcosa di simile a:
#include <iostream>
#include <string>
struct base
{
};
class test
{
  std::string dati_;
public:
  test() : dati_( "f(void*)\n" ) {}
//    void f() { std::cout << "test::f\n"; }
  static void f( void* This )
    { std::cout << static_cast< test* >(This)->dati_; }
};
void foo(void* obj, void (*mf)(void* ))
{
    mf( obj );
}
int main()
{
    test t;
    foo(&t, test::f );
    std::cin.get();
}
Cosi` dovresti eliminare qualsiasi overhead, mentre la scrittura di f e`
un po' piu` noiosa, ricorda il C.
Per quanto riguarda il nuovo std non so nulla. Ma mi pare che i tempi
siano comunque abbastanza lunghi.
ciao
Massimo

 operator-> e` considerata dereferenziazione?
Il problema e' che questa e' si l'implementazione piu' comune, ma non e'
una semantica imposta dallo standard; formalmente l'applicazione degli
operatori * e -> su un puntatore nullo porta gia' a comportamento
indefinito, anche se non si usa il risultato. Se non sbaglio infatti
esistono piattaforme per le quali gia' il calcolo di un puntatore non
valido genera un trap hardware.
Ciao,
Nicola


Proverò a dare una occhiata, grazie.
Il mio problema è comunque decidere quale soluzione adottare.
Come ho già scritto devo fare un wrapper per una libreria C.
La soluzione che sto adottando è la seguente:
template<class Instance> class Base
{
    Instance* ptr;
    void (Instance::*mf)(int);
    static void C_callback(int x)
    {
        (ptr->*mf)(x);
    }
    void set_callback(Intance* p, void (Instance::*pf)(int))
    {
        ptr = p;
        mf = pf;
        // passa Base<Instance>::callback_C alla funzione C
    }
    // ...
};
// classe definita dall'utente e contenente le funzione che vuole usare
// come callback
class Istance: public Base<Instance>
{
    void my_callback(int);
    // ...
    Instance()
    {
        set_callback(this, &Instance::my_callback);
    }
};
Sicuramente tale soluzione (è unspecified o undefined?) non è Standard ma mi
funziona con il VCC e gpp.
Il problema di questo approccio è che per l'utente non cambia molto rispetto ad
usare le funzioni virtuali (ossia la classe contenente le funzioni
callback deve essere dirivata da Base); per questo sto cercando un metodo più
flessibile e i
delegate lo sono.


 ma mi funziona con il VCC e gpp.
Scusa, forse mi sono perso qualcosa, ma non capisco perchè non sarebbe
standard.
&Instance::my_callback viene passato a una funzione che si aspetta un
void(Instance::*pf)(int). Dov'è il problema?
Max

 valido genera un trap hardware.
va bene, grazie



No, probabilmente mi sono spiegato male: &Instance::my_callback  viene passata
ad una funzione C che si aspetta un void (*pf)(int).
Ciao  Manlio Perillo


Riprendo il discorso perchè controllando con il VCC ho notato che in effetti
c'è una sola chiamata a funzione.
Infatti in call_memfun la chiamata a mf viene espansa inline (dato che è nota
a compile-time). Se la funzione è virtuale invece non viene espansa.
Purtroppo il VCC (7 beta 2) non digerisce il codice:
func_ = &call_memfunc<T, Result, Arg, mf>;
quindi non ho potuto verificare se anche func_ viene espansa inline.
Qualcuno potrebbe provare il codice con il KAI C++?.
C'è un altra cosa da aggiungere alla tua implementazione di delegate
che lo rende veramente molto appetibile.
Considera la seguente aggiunta (riposto il codice intero):
template<class T, class Result, class Arg, Result (T::*mf)(Arg)>
inline Result call_memfunc(void* p, Arg a)
{
    return (static_cast<T*>(p)->*mf)(a);   // expand mf inline
}
template<class Result, class Arg, Result (*f)(Arg)>
inline Result call_func(void* p, Arg a)
{
    return (*f)(a);   // expand f inline
}
template<class Result, class Arg>
class delegate
{
    private:
        typedef Result(*func_t)(void*, Arg);
        void*  obj_;
        func_t func_;
    public:
        template<class T, Result(T::*mf)(Arg)>
        void bind(T* p)
        {
            obj_ = p;
            func_ = &call_memfunc<T, Result, Arg, mf>;
        }
        template<Result (*f)(Arg)>
        void bind()
        {
            obj_ = 0;
            func_ = &call_func<Result, Arg, f>;
        }
        Result operator()(Arg a) { return func_( obj_, a); }
};
(Il codice compila con il gpp mentre con il BCC 5.5 provoca un internal compiler
error).
In  questo modo si può 'legare' sia una funzione membro che una normale
funzione.
Ciò vuol dire che una libreria che offra supporto per funzioni callback
può presentare la registrazione in questo modo (ad esempio):
void set_callback(delegate<void, int> d);
Le chiamate a f e mf vengono espanse inline e a mio avviso un buon compilatore
potrebbe essere in grado di espandere inline anche func_.
Questo significa un meccanismo di delegazione senza alcun overhead e veramente
molto
flessibile.
Qualche controindicazione?
Saluti  Manlio Perillo

 [CUT]
A rischio di essere monotono, ecco (a titolo di esempio) una versione di
delegate in cui tutte le chiamate a funzione sono espanse inline:
template<class Result, class Arg, class T, Result (T::*mf)(Arg)>
inline Result call_func(void* p, Arg a)
{
    return (static_cast<T*>(p)->*mf)(a);   // expand mf inline
}
template<class Result, class Arg, Result (*f)(Arg)>
inline Result call_func(void* p, Arg a)
{
    return (*f)(a);   // expand f inline
}
template<class Result, class Arg, Result (*auxf)(void*, Arg)>
class delegate
{
    private:
        void* obj_;
    public:
        template<class T>
        delegate(T* p)
        : obj_(p) {  }
        delegate()
        : obj_(0) {  }
        Result operator()(Arg a) { return auxf(obj_, a); } // expand auxf inline
};
// esempio
#include <iostream>
struct pippo
{
    int i;
    pippo(int ii)
    : i(ii) {  }
    int foo(int j)
    {
        std::cout << "pippo::foo\n";
        return i + j;
    }
};
int pippo_foo(int j)
{
    std::cout << "pippo_foo\n";
    return j;
}
template<int (*f)(void*, int)>
inline void callback(delegate<int, int, f> d)
{
    int r = d(1);
    std::cout << r << '\n';
}
int main()
{
    pippo p(9);
    delegate< int, int, &call_func<int, int, pippo, &pippo::foo> > d(&p);
    callback(d);
    delegate< int, int, &call_func<int, int, &pippo_foo> > d2;
    callback(d2);
}
Il codice compila con il BCC 5.5 ed il gpp.
Saluti  Manlio Perillo

 > >         // passa Base<Instance>::callback_C alla funzione C
 
quindi alla funzione C viene passata una funzione static, cosa che in sé
non è problematica. Non vedo dove &Instance::my_callback verrebbe passata
a "una funzione C che si aspetta un void (*pf)(int)".
Max

"Manlio Perillo" ha scritto
:
: A rischio di essere monotono, ecco (a titolo di esempio) una versione di
: delegate in cui tutte le chiamate a funzione sono espanse inline:
:
:
[CUT]
: int main()
: {
:     pippo p(9);
:
:     delegate< int, int, &call_func<int, int, pippo, &pippo::foo> > d(&p);
:     callback(d);
:
:     delegate< int, int, &call_func<int, int, &pippo_foo> > d2;
:     callback(d2);
: }
:
:
: Saluti  Manlio Perillo
:
Penso che non ci siamo capiti sul significato di delegate, probabilmente
gli ho attribuito un significato errato.
Io intendevo, per esempio, la possibilita` di passare ad _una_
funzione, un metodo di un oggetto che veniva scelto at run-time,
oggetto di cui _la_ funzione non e` a conoscenza, cioe` _la_
funzione puo` essere scritta prima, mentre e` poi possibile
aggiungere nuovi oggetti senza alcuna relazione tra loro. Per
capirci, elaborando il tuo esempio iniziale nella direzione che
intendevo io, avresti:
#include <cassert>
#include <iostream>
#include <string>
#include <vector>
struct base
{
  base() : dati_( "base::bar( void* )\n" ) {}
  static void bar( void* This )
    { std::cout << static_cast< base* >(This)->dati_; }
private:
  std::string dati_;
};
char test_dati[] = "test::f( void* )\n";
struct test
{
  test() : dati_( test_dati, test_dati + sizeof test_dati ){}
  static void f( void* This ) {
       std::cout.write( &(static_cast< test* >(This)->dati_)[0],
       sizeof test_dati);
    }
private:
  std::vector<char> dati_;
};
struct delegate
{
  delegate() : obj_( 0 ), fun_( 0 ) {}
  void bind( void* obj, void (*fun)(void*) ) { obj_=obj; fun_=fun; }
  void operator ()() const { assert(obj_ && fun_); fun_( obj_ ); }
private:
  void* obj_;
  void (*fun_)( void* );
};
void foo( const delegate& del )
  { del( ); }
int main()
{
    test t;
    base b;
    delegate del;
    std::cout << "Pigia b per base, t per test" << std::endl;
    switch( std::cin.get() )
    {
      default:  del.bind( &t, test::f ); break;
      case 'b': del.bind( &b, base::bar );
    }
    foo( del );
}
foo sarebbe _la_ funzione. Che e` scritta senza sapere di che tipo
sono gli oggetti che le vengono passati.
base e test sono due classi slegate tra loro, che hanno dati diversi
e funzioni diverse
delegate e` il trait d'union.
A questo punto si possono aggiungere altri oggetti senza necessita`
di derivare l'uno dall'altro o da un oggetto base comune, e con la
possibilita` di scegliere tra varie funzioni membro (purche` con la
stessa signature). Ovviamente la cosa si complica quando le funzioni
membro da chiamare sono virtuali.
ciao
Massimo

 delegate in cui tutte le chiamate a funzione sono espanse inline:
Sì, però in questa versione di delegate<> svanisce quello che, almeno per
me, rappresentava l'obiettivo chiave: avere un oggetto il cui tipo
effettivo dipendesse *esclusivamente* dall'argomento e dal tipo di ritorno
del metodo e non dal tipo degli oggetti che gli si vogliono associare. Mi
interessava, cioè, una forma di polimorfismo run-time, che mi permettesse,
ad esempio, di instanziare vettori di delegate<> associati a oggetti di
tipi non necessariamente imparentati tra loro, che è poi il senso dei
delegate e delle closure in C# e in Delphi.
Max


 non è problematica.
In un numero di C++ Report di Carlo Pescio era indicato che tale
approccio non era conforme allo standard.
La funzione C si aspetta una funzione 'extern "C"'.
Ciao  Manlio Perillo


Per rispondere anche a Massimo: il mio era solo un esempio.
Sto cercando di trovare una implementazione in cui tutte le chiamate
tramite puntatori a funzioni vengano espanse inline (a meno di
non essere virtual ovviamente).
L'unica cosa a cui per ora sono giunto č una versione della tua delegate
che usa un puntatore a funzione costante (inizializzato quindi tramite
costruttore) e senza (ovviamente) il metodo bind.
Quindi la funzione č fissata all'atto di creazione della classe.
Ciao  Manlio Perillo