Newsgroup: it.comp.lang.c++
Titolo: Determinare il tipo di ritorno di una funzione sovraccaricata

Supponiamo di avere una funzione con piu` overload (in questo caso 2, ma 
potenzialmente molti di piu`):
double foo(int, int);
char foo(const char*, double);
Supponiamo anche che io debba "tenere traccia" di ogni chiamata a foo, 
indipendentemente dal tipo dei parametri: la cosa piu` ovvia da fare e` 
IMHO:
template<class First, class Second>
void bar(First x, Second y)
{
        cout << "Calling foo...\n";
        foo(x,y);
}
Grazie a questo template ora posso tranquillamente scrivere
bar(1,2); // chiama foo(int, int);
bar("Anto", 3.0); // chiama  foo(const char*, double);
// ecc.
Il problema si pone quando voglio considerare anche il valore di 
ritorno: una possibile idea potrebbe essere questa:
template<class Result, class First, class Second>
Result bar(First x, Second y)
{
        cout << "Calling foo...\n";
        return foo(x,y);
}
Pero` questa tecnica ha l'inconveniente di costringere l'utente a 
scrivere esplicitamente il tipo di ritorno:
bar<double>(1,2);
bar<char>("Anto", 3.0);
// ecc.
Mi chiedo: e` possibile evitare questo? D'altronde una volta che il 
compilatore ha determinato quale overload chiamare sa automaticamente 
anche quale e` il suo tipo di ritorno: come fare per "passare" il tipo 
di ritorno a bar?
Tutto il problema nasce perche`, in realta`, le varie foo() sono metodi 
sovraccaricati di una classe passata come parametro di un template, 
quindi avrei bisogno di un metodo generale per poter "redirigere" tutti 
gli overload di quel metodo.
grazie
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

 scrivere esplicitamente il tipo di ritorno:
e' molto difficile che capiti qualcosa del genere, IMHO
il tipo di ritorno e' molto strettamente legato a quelli d'ingresso, e 
nella mia esperienza si ricava sempre da questi, ad esempio 
template<class Result, class First, class Second>
std::pair<First, Second> bar(First x, Second y)
se poi devi convertire, i casi sono due:
se la conversione e' banale, basta un cast (ad esempio int -> double)
altrimenti si usa una classe proxy o un costruttore esplicito
int p = 3;
char t[]= "stringa";
MyClass T(bar(p,t));
dove MyClass ha un costruttore explicit che accetta uno std::pair<int, 
char[]>

 [CUT]
prova con:
template<class Result, class Arg1, class Arg2>
Result bar(Arg1 a, Arg2 b, Result (*f)(Arg1, Arg2))
{
    return f(a, b);
}
bar(1, 1, &foo);  // ok
bar(1, 1u, &foo); // errore
P.S.
Ad essere sincero non sono certo di capire perchè funziona....
Ciao  Manlio Perillo


Probabilmente perche' specificando nel prototipo di bar result come tipo di
ritorno di un puntatore a funzione che prende in input arg1,arg1, in fase di
istanziazione del template il compilatore identifica Result con il corretto
overload, quindi non c'e' ambiguita' sul tipo di ritorno.
Ivan Molella

 bar(1, 1u, &foo); // errore
bella idea (io non ci avevo neppure pensato), ma purtroppo non fa al 
caso mio. In realta` la funzione bar e` un operator binario, quindi non 
ho la possibilita` di passargli anche un terzo argomento. In compenso la 
tua idea mi ha dato qualche spunto di riflessione interessante: mi sono 
avvicinato molto alla soluzione, ma per un soffio non riesco ad 
accedervi; in termini meno poetici, ecco cosa ho pensato:
// tentativo di creare un template che esponga il tipo di ritorno di una
// funzione
template<class T>
struct Helper {
        typedef T Result;
};
template<class left, class right, class res>
Helper<res> Return_type( res(*)(left,right) )
{
        return Helper<res>;
};
Una volta scritto questo posso fare:
double foo(int, char);
int foo(double, int);
Return_type<int,char>(foo);
a questo punto ho a disposizione un oggetto il cui tipo mi espone la 
typedef voluta (ovvero Helper<>::Result) ma a cui non sono in grado di 
accedre! :-(
Purtroppo l'operatore di scope vuole come operando sinistro il nome di 
una classe o namespace, e non mi consente di scrivere cose del tipo:
Return_type<int,char>(foo)::Result; // ovviamente errore :-((
La cosa triste e` che se avessimo a disposizione l'operatore typeof 
(disponibile come estensione di g++ - Alexandrescu in "Modern C++ 
Design" dice che c'e` una proposta di inserirlo nel prossimo standard) 
la cosa sarebbe tremendamente semplice: questo codice compila e funziona 
con g++ 3:
template<class left, class right, class res>
res Return_type( res(*)(left,right) ); // non c'e` bisogno di definirla
....
typeof(Return_type<int,char>(foo)) mia_variabile = 4.345;
Non c'e` proprio alcuna soluzione restando nell'ambito del C++ standard? 
Ho la sensazione che non ci sia (altrimenti non avrebbe senso discutere 
dell'operatore typeof)...
ciao Anto


 nella mia esperienza si ricava sempre da questi, ad esempio
non e` sempre vero: il tipo di ritorno e` generalmente indipendente da 
quelli di ingresso: io sto scrivendo una libreria generica e dovrei 
consentrire all'utente la massima liberta` possibile, senza doverlo 
costringere nella scelta del tipo di ritorno.
ciao Anto


 discutere dell'operatore typeof)...
provato con
template<class T>
struct Helper {
    T& operator()(){
      static T t;
      return t;
    }
};
template<class left, class right, class res>
Helper<res>& ReturnType(res(*)(left,right))  {
  static Helper<res> h;
  return h;
}
ReturnType<int, char>(foo)()
non ho il tempo di vedere se compila, ma a naso direi di si
ho messo gli static nel caso siano ogetti grandi, l'unico vincolo e' 
che gli T devono avere costruttore di default e di copia (lavorando
coi puntatori, cio' si evita, cmq)

 ReturnType<int, char>(foo)()
avevo gia` fatto qualcosa del genere, ma il problema e` che con la riga 
qua sopra ottengo una istanza di una classe e non un tipo :-(
In pratica non e` possibile scrivere cose del tipo
ReturnType<int,char>(foo)() mia_funzione(int i, char c)
{
        return foo(i,c);
}
grazie lo stesso dell'aiuto.


 grazie lo stesso dell'aiuto.
allora devi rendere la f template
/* metacodice */
template <class T, (altri param.)> 
T mia_funzione(T, parametri);
mia_funzione(ReturnType<int,char>(foo)(), i, c)

 mia_funzione(ReturnType<int,char>(foo)(), i, c)
in questo modo funziona ma purtoppo la mia "mia_funzione" e` in realta` 
un operatore binario a cui non posso passare tre parametri; sono sempre 
piu` convinto che una soluzione standard non esista :-(
ciao anto


 piu` convinto che una soluzione standard non esista :-(
umm il problema e' che non vi e' una corrispondenza biunivoca tra 
l'insieme delle funzioni che accettano due argomenti, e gli operatori 
binari che accettano due argomenti:
ad esempio sono diverse
int   foo(int, char); 
long  foo(char, char);
da   
float bar(int, char);
char  bar(char, char);
mentre gli operatori binari sovraccarichi non possono avere le stesse 
variazioni: se e' un operatore, la sua funzione e' gia' implicita nel 
nome
passare con solo 2 argomenti deducibili un generico terzo argomento 
indipendente dai due, in quanto con gli stessi argomenti posso avere due 
funzioni diverse con tipi diversi, e' ovviamente impossibile.
quindi *e' necessario* un terzo argomento. Questo in linea generale, se 
poi per ogni *tipo* di operatore ti serve *una* sola funzione 
sovraccarica, allora si puo' fare
se potessi essere piu' chiaro sul codice...

 due funzioni diverse con tipi diversi, e' ovviamente impossibile.
confesso che subito non capivo cosa volessi dire, ma adesso penso di 
aver capito; credo che un po' di codice possa essere utile per capire 
meglio (ovviamente in questo contesto la cosa appare insensata - ma ti 
assicuro che ha una sua logica):
class BigNumber { ... };
class VeryBigNumber { ... };
struct operator_add {
        static BigNumber exec(BigNumber, BigNumber) { ... }
        static VeryBigNumber exec(VeryBigNumber, VeryBigNumber) { ... }
};
template<class Left, class Right>
[Tipo_di_ritorno?] operator+(Left l, Right r)
{
        return operator_add::exec(l,r);
}
Ripeto, in questo caso sembra un inutile groviglio, ma nel codice reale 
ha un senso; ovviamente sto cercando di scrivere una serie di componenti 
generiche, quindi nel codice reale anche la struttura "operator_add" e` 
in realta` un parametro di template, ma non e` il caso di complicare le 
cose su questa sede (quando avro` finito potrete leggere un mio articolo 
su Programmazione.it).
Ritornando al codice in questo caso il problema del tipo di ritorno 
sembra non presentarsi, dato che e` sempre uguale al primo (o secondo) 
operando: pero` questa e` una limitazione un po' forte e mi piacerebbe 
dare all'utente la possibilita` di scegliere il tipo di ritorno che 
preferisce.
ciao Anto


 preferisce.
ok, allora, FWIK non si puo', vista la signature delle funzioni in gioco

 gioco
ok, forse mi ricredo: se per operator+ tu chiami *sempre* una delle 
varianti di operator_add.exec, forse si puo' fare
struct proxy_{}; // base
template <typedef T>
class Proxy : public proxy_ {
  T t_;
public:
  Proxy(const T& t): t_(t) {}
  T operator()() const {return t_;}
  void operator()(const T& t) {t_ = t;}
}
template<class T>
struct Helper {
    T& operator()(){
      static T t;
      return t;
    }
};
template<class left, class right, class res>
Helper<res>& ReturnType(res(*)(left,right))  {
  static Helper<res> h;
  return h;
}
class BigNumber { ... };
class VeryBigNumber { ... };
struct operator_add {
        static BigNumber exec(BigNumber, BigNumber) { ... }
        static VeryBigNumber exec(VeryBigNumber, VeryBigNumber) { ... }
};
template<class Left, class Right>
proxy_* operator+(Left l, Right r) {
        // conoscendo il nome della funzione, posso tranquillamente usarla
        // per istanziarmi il proxy di ritorno
        // nota che con una f. qualunque questo non e' possibile
        // senza poter passare un altro parametro esplicito
        Proxy< ReturnType<l,r>(operator_add::exec)() >* p;
        p = new Proxy< ReturnType<l,r>(operator_add::exec)() >();
        p->operator()(operator_add::exec(l,r)());
        return p;
}
e il risultato poi lo puoi richiamare con p->operator()();
non so se questo "mostro" teorico compila facilmente, e come al solito 
non ho molto tempo, basta guardare a che ora sto leggendo il NG... prova 
e fammi sapere, di sicuro si puo' migliorare la macchinosita' degli 
operatori(), e dei puntatori

 non ho molto tempo, basta guardare a che ora sto leggendo il NG...
te lo dico io, non va: non si puo' istanziare una classe ttemplate ccon 
una funzione
comunque, credo ci si possa riuscire con un Command e un Proxy, in 
pratica trasformando il puntatore a funzione in un puntatore a funzione 
membro di una classe che esegue la funzione ritornando un 
Proxy<Command::reult_type>, che si ritorna
ci penso su e ti faccio sapere

 Anto
Ciao,
Ultimamente non ho seguito assiduamente il NG, quindi ti
rispondo un po' in ritardo.
Sembra che il tuo problema principale sia determinare il tipo di 
ritorno di una funzione overloaded.
Probabilmente la strada da intraprendere e' quella di usare
la partial specialization invece degli overload.
Pensavo ad esempio a questo:
//
// Classe per il caso generale
// in questo caso il tipo di ritorno equivale
// all'argomento del primo tipo
//
template <class T1, class T2>
struct operator_add
{
   typedef T1 result_type;
   typedef T1 arg1_type;
   typedef T2 arg2_type;
   static result_type exec(arg1_type a1, arg2_type a2) { return a1 + a2 ; }
};
//
// per ogni specializzazione definisci i
// tre tipi coinvolti e implementi il metodo statico 
// exec
//
template <>
struct operator_add<int, const char *>
{
   typedef double result_type;
   typedef int arg1_type;
   typedef const char * arg2_type;
 
   static result_type exec(arg1_type a1, arg2_type a2) { return 1.2; }
};
//
// definisci una funzione template sui due argomenti
// che si occupa di invocare il metodo exec 
// dell'operatore in questione
//
template <typename T1, typename T2>
operator_add<T1,T2>::result_type add(T1 a1, T2 a2) 
{
   return operator_add<T1,T2>::exec( a1, a2 );
}
int main() 
{
   int n = add( 1 , 2 );                // torna 3
   double d = add( 1 , "pippo" );       // torna 1.2
}
che ne pensi ?
Saluti,
Fabio

 ci penso su e ti faccio sapere
prova questo...
Ovviamente si puo' fare meglio, ad esempio usando reference al posto di
puntatori, usare un bridge per il tipo di ritorno, etc, pero' compila 
con 
$g++ -Wall -pedantic prova.cc 
#include <iostream>
struct proxy_{
  virtual void set(){}
};
template <typename T>
class Proxy : public proxy_ {
  T t_;
public:
  Proxy(T t): t_(t) {}
  operator T() const {return t_;}
  void set(const T& t) {t_ = t;}
};
struct command_ {
  virtual operator proxy_*() const = 0;
};
template<class Left, class Right, class Res>
struct Command : virtual command_ {
  typedef Res result_type;
  typedef result_type(*Func)(Left,Right);  
private:
  Func exec;
  Left l_;
  Right r_;
public:
  Command(Func f, Left l, Right r): exec(f), l_(l), r_(r){}
  operator proxy_*() const {
    static Proxy<Command::result_type> 
      p=Proxy<Command::result_type>(Command::result_type());
    p.set(exec(l_,r_));
    return &p;
  }
};
template<class left, class right, class res>
Command<left, right, res>* CreateCommand(res(*fun) (left,right), left l, 
right r )  {
  static Command<left, right, res> c(fun, l, r);
  return &c;
}
struct BigNumber {};
struct VeryBigNumber { };
ostream & operator << (ostream& os, BigNumber b) {
 return os << "BigNumber";
}
ostream & operator << (ostream& os, VeryBigNumber b) {
return os << "VeryBigNumber";
}
struct operator_add {
  static BigNumber exec(BigNumber a, BigNumber b) { 
    std::cout << "BN add\n"; return a; 
  }
  static VeryBigNumber exec(VeryBigNumber a, VeryBigNumber b) { 
    std::cout << "VBN add\n"; return b; 
  }
};
template<class Left, class Right>
proxy_* operator+(Left& l, Right& r) {
  command_* c = CreateCommand<Left, Right>(operator_add::exec, l, r);
  proxy_* p = c->operator proxy_*();        
  return p;
}
int main() {
  BigNumber a;
  VeryBigNumber b;  
  // qui e' meglio un bridge che trasformi un Proxy<T>* in T
  Proxy<BigNumber>* A = static_cast< Proxy<BigNumber> *>(a+a);
  Proxy<VeryBigNumber>* B = static_cast< Proxy<VeryBigNumber> *>(b+b);
  // cosi poi qui scrivo solo 
  // BigNumber A = a+a;
  std::cout << (BigNumber)*A << " " << (VeryBigNumber)*B << std::endl;
  // e qui usi direttamente B
}


Non so se potrebbe fare al caso tuo, ma hai provato a dare una occhiata alle
tecniche
presenti nella 'template metaprogramming'?
Ci sono alcuni link alla home page di Blitz++.
Ciao  Manlio Perillo

 }
Come ho suggerito nel mio altro post, credo che la soluzione vada
cercata nella partial specialization di classi template.
In ogni caso, temo che non sia possibile determinare 'automagicamente' 
a compile time il tipo di ritorno di una funzione overloaded
ma va in qualche modo specificata esplicitamente. 
Saluti,
Fabio

 ma va in qualche modo specificata esplicitamente.
Uhm, i meta template si usano con f. ricorsive, mi pare. Come 
procederesti?

 $g++ -Wall -pedantic prova.cc
 
idea interessante... purtroppo in questo momento il tempo e` tiranno, 
appena posso lo guardo con calma e poi ti dico.
grazie per l'aiuto 
ciao Anto


 che ne pensi ?
molto interessante: in effetti avevo pensato a qualcosa di simile, ma 
molto piu` (inutilmente) intricato: come spesso accade la soluzione 
semplice e` la migliore.
Inoltre ero partito anche io dall'idea di usare delle typedef per 
indicare i tipi degli argomenti e di ritorno, ma avevo abbandonato per 
la necessita` degli overload.
Pero` ad una occhiata veloce il tuo codice mi pare affetto da un 
problema: non gestisce le conversioni implicite; ad esempio, supponi di 
avere, sullo stile dell'esempio originale, un qualcosa del tipo:
struct Prova {
        Prova(int);
        Prova();
};
struct operator_add {
        int exec(void*, void*);
        Prova exec(Prova, Prova);
};
mi aspetterei di poter scrivere:
        Prova p;
        1 + p;
        p + 1;
        &p + &p;
come si puo` fornire una soluzione generale a questo problema, senza 
dover specializzare esplicitamente tutte le possibili conbinazioni di 
conversioni implicite?
ciao e grazie


 procederesti?
Scusa ho letto male, non era il tuo il msg sul meta template.
Comunque sull'overloading, ti devo contraddire: Carlo Pescio, sulla sua 
(purtroppo abbandonata) NewsLetter Informer, numero 7 dell'Aprile 99, 
sezione No limits da' proprio una soluzione all'overloading di funzioni 
sul solo tipo di ritorno, usando appunto un proxy. Peccato non permetta 
di fare quello che chiede Cuni, cioe' di far richiamare ad un operatore 
una funzione qualunque, per questo ho usato un command + un proxy
se sei interessato alla NewsLetter, devi chiedere a pescio, di solito 
manda un file zipppato contenente il txt di tutte le uscite (solo 12)
http://www.programmers.net/artic/Pescio
 

 Ci sono alcuni link alla home page di Blitz++.
grazie del link, vado a vedere.


   // BigNumber A = a+a;
cosa intendi con "bridge"? A naso immagino che tu intenda un qualcosa 
del tipo:
template<class T>
class Bridge {
        Proxy<T>* ptr;
public:
        Bridge(Proxy<T>* p): ptr(p) { }
        operator T() { return (T) p; }
};
a questo punto operator+ ritornerebbe una istanza di Bridge, ho capito 
bene?
Se ho capito bene penso che purtroppo non sia quello che mi serva: ho 
bisogno che il tipo restituito sia *esattamente* T, per essere sicuro di 
non creare casini con le conversioni implicite. Altrimenti immagina 
questo:
class X { };
class Y {
        Y(X); // conversione implicita
};
void f(Y);
X a,b;
f(a+b); // non compila
purtroppo questo non e` un compromesso accettabile per i potenziali 
utenti della mia libreria.
Oppure ho capito male e il bridge e` un'altra cosa.
ciao Anto


 Oppure ho capito male e il bridge e` un'altra cosa.
quello e' un tipo di bridge, ma leggi la riposta a f. lopiano, dovrebbe 
bastare un op. template aggiuntivo


Dovresti cercare, in particolare, qualcosa sulla expression template.
Ciao  Manlio Perillo