Come posso chiamare portabilmente una funzione C ++ che accetta un carattere ** su alcune piattaforme e un carattere const ** su altre?


91

Sulle mie macchine Linux (e OS X), la iconv()funzione ha questo prototipo:

size_t iconv (iconv_t, char **inbuf...

mentre su FreeBSD assomiglia a questo:

size_t iconv (iconv_t, const char **inbuf...

Vorrei che il mio codice C ++ si compilasse su entrambe le piattaforme. Con i compilatori C, il passaggio di a char**per un const char**parametro (o viceversa) tipicamente genera un semplice avvertimento; tuttavia in C ++ è un errore fatale. Quindi se passo a char**, non si compilerà su BSD, e se passo a const char**non si compilerà su Linux / OS X. Come posso scrivere codice che compili su entrambi, senza ricorrere al tentativo di rilevare la piattaforma?

Un'idea (fallita) che avevo era quella di fornire un prototipo locale che sovrascriva qualsiasi fornito dall'intestazione:

void myfunc(void) {
    size_t iconv (iconv_t, char **inbuf);
    iconv(foo, ptr);
}

Questo fallisce perché ha iconvbisogno del collegamento C e non puoi inserire extern "C"una funzione (perché no?)

La migliore idea di lavoro che ho avuto è quella di lanciare il puntatore alla funzione stesso:

typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);

ma questo ha il potenziale per mascherare altri errori più gravi.


31
Domanda infernale per la tua prima volta su SO. :)
Almo

24
Registra un bug su FreeBSD. L'implementazione POSIX di iconvrichiede inbufche non sia const.
dreamlax

3
Trasmettere la funzione in questo modo non è portatile.
Jonathan Grynspan

2
@dreamlax: è improbabile che l'invio di una segnalazione di bug abbia un effetto; la versione corrente di FreeBSD apparentemente ha già iconvsenza const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo

2
@larsmans: Buono a sapersi! Non ho mai usato FreeBSD, ma è bene sapere che l'ultima versione supporta lo standard più recente.
dreamlax

Risposte:


57

Se quello che vuoi è solo chiudere un occhio su alcuni problemi con const, puoi usare una conversione che offusca la distinzione, cioè rende char ** e const char ** interoperabili:

template<class T>
class sloppy {}; 

// convert between T** and const T** 
template<class T>
class sloppy<T**>
{
    T** t;
    public: 
    sloppy(T** mt) : t(mt) {}
    sloppy(const T** mt) : t(const_cast<T**>(mt)) {}

    operator T** () const { return t; }
    operator const T** () const { return const_cast<const T**>(t); }
};

Quindi più avanti nel programma:

iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);

sloppy () prende a char**o a const char*e lo converte in a char**o a const char*, qualunque sia il secondo parametro di iconv richiesto.

AGGIORNAMENTO: modificato per utilizzare const_cast e chiamare sciatto non come cast.


Funziona abbastanza bene e sembra essere sicuro e semplice senza richiedere C ++ 11. Ci vado! Grazie!
ridiculous_fish

2
Come ho detto nella mia risposta, credo che questo viola rigorosa aliasing in C ++ 03, quindi in questo senso essa non richiede C ++ 11. Potrei sbagliarmi, però, se qualcuno vuole difenderlo.
Steve Jessop

1
Per favore non incoraggiare i cast in stile C in C ++; a meno che non sbaglio, puoi chiamare l' sloppy<char**>()inizializzatore direttamente lì.
Michał Górny

Ovviamente è sempre la stessa operazione di un cast in stile C, ma utilizza la sintassi alternativa C ++. Immagino che potrebbe scoraggiare i lettori dall'usare calchi in stile C in altre situazioni. Ad esempio, la sintassi C ++ non funzionerebbe per il cast a (char**)&inmeno che non si crei prima un typedef per char**.
Steve Jessop

Buon trucco. Per completezza, potresti probabilmente farlo (a) prendendo sempre un const char * const *, supponendo che la variabile non debba essere alterata o (b) parametrizzandola con due tipi qualsiasi e facendola eseguire il cast di const tra di loro.
Jack V.

33

È possibile chiarire le ambiguità tra le due dichiarazioni controllando la firma della funzione dichiarata. Ecco un esempio di base dei modelli richiesti per ispezionare il tipo di parametro. Questo potrebbe essere facilmente generalizzato (o potresti usare i tratti della funzione di Boost), ma questo è sufficiente per dimostrare una soluzione per il tuo problema specifico:

#include <iostream>
#include <stddef.h>
#include <type_traits>

// I've declared this just so the example is portable:
struct iconv_t { };

// use_const<decltype(&iconv)>::value will be 'true' if the function is
// declared as taking a char const**, otherwise ::value will be false.
template <typename>
struct use_const;

template <>
struct use_const<size_t(*)(iconv_t, char**, size_t*, char**, size_t*)>
{
    enum { value = false };
};

template <>
struct use_const<size_t(*)(iconv_t, char const**, size_t*, char**, size_t*)>
{
    enum { value = true };
};

Ecco un esempio che dimostra il comportamento:

size_t iconv(iconv_t, char**, size_t*, char**, size_t*);
size_t iconv_const(iconv_t, char const**, size_t*, char**, size_t*);

int main()
{
    using std::cout;
    using std::endl;

    cout << "iconv: "       << use_const<decltype(&iconv)      >::value << endl;
    cout << "iconv_const: " << use_const<decltype(&iconv_const)>::value << endl;
}

Una volta rilevata la qualificazione del tipo di parametro, è possibile scrivere due funzioni wrapper che chiamano iconv: una che chiama iconvcon un char const**argomento e una che chiama iconvcon un char**argomento.

Poiché la specializzazione del modello di funzione dovrebbe essere evitata, utilizziamo un modello di classe per eseguire la specializzazione. Nota che rendiamo anche ogni invokers un modello di funzione, per assicurarci che venga istanziata solo la specializzazione che usiamo. Se il compilatore tenta di generare codice per la specializzazione sbagliata, verranno visualizzati degli errori.

Quindi includiamo l'uso di questi con un call_iconvper rendere la chiamata semplice come chiamare iconvdirettamente. Quello che segue è un modello generale che mostra come questo può essere scritto:

template <bool UseConst>
struct iconv_invoker
{
    template <typename T>
    static size_t invoke(T const&, /* arguments */) { /* etc. */ }
};

template <>
struct iconv_invoker<true>
{
    template <typename T>
    static size_t invoke(T const&, /* arguments */) { /* etc. */ }
};

size_t call_iconv(/* arguments */)
{
    return iconv_invoker<
        use_const<decltype(&iconv)>::value
    >::invoke(&iconv, /* arguments */);
}

(Quest'ultima logica potrebbe essere ripulita e generalizzata; Ho cercato di rendere esplicita ogni parte di essa per rendere, si spera, più chiaro come funziona.)


3
Bella magia lì. :) Vorrei votare perché sembra che risponda alla domanda, ma non ho verificato che funzioni, e non conosco abbastanza il C ++ hardcore per sapere se lo fa solo guardandolo. :)
Almo

7
Nota: decltyperichiede C ++ 11.
Michał Górny

1
+1 Lol ... quindi per evitare un #ifdefcontrollo per la piattaforma ti ritroverai con 30 righe di codice dispari :) Un bell'approccio però (anche se negli ultimi giorni mi sono preoccupato guardando le domande in modo che le persone che non lo fanno capisco davvero cosa stanno facendo hanno iniziato a usare SFINAE come un martello d'oro ... non è il tuo caso, ma temo che il codice diventerà più complesso e difficile da mantenere ...)
David Rodríguez - dribeas

11
@ DavidRodríguez-dribeas: :-) Seguo semplicemente la regola d'oro del C ++ moderno: se qualcosa non è un modello, chiediti "perché non è un modello?" quindi trasformalo in un modello.
James McNellis

1
[Prima che qualcuno prenda troppo sul serio quell'ultimo commento: è uno scherzo. Una specie di...]
James McNellis

11

Puoi usare quanto segue:

template <typename T>
size_t iconv (iconv_t i, const T inbuf)
{
   return iconv(i, const_cast<T>(inbuf));
}

void myfunc(void) {
  const char** ptr = // ...
  iconv(foo, ptr);
}

Puoi passare const char**e su Linux / OSX passerà attraverso la funzione template e su FreeBSD andrà direttamente a iconv.

Svantaggio: consentirà chiamate come quelle iconv(foo, 2.5)che metterà il compilatore in ricorrenza infinita.


2
Bello! Penso che questa soluzione abbia un potenziale: mi piace l'uso della risoluzione del sovraccarico per selezionare il modello solo quando la funzione non è una corrispondenza esatta. Per funzionare, tuttavia, const_castdovrebbe essere spostato in un add_or_remove_constche scava in T**per rilevare se lo Tè conste aggiungere o rimuovere la qualifica a seconda dei casi. Questo sarebbe ancora (molto) più semplice della soluzione che ho dimostrato. Con un po 'di lavoro, potrebbe anche essere possibile far funzionare questa soluzione senza const_cast(cioè, utilizzando una variabile locale nel tuo iconv).
James McNellis

Mi sono perso qualcosa? Nel caso in cui il real iconvnon sia const, non viene Tdedotto come const char**, il che significa che il parametro inbufha tipo const T, che è const char **const, e la chiamata a iconvnel modello chiama solo se stessa? Come dice James, però, con una modifica adeguata al tipo Tquesto trucco è la base di qualcosa che funziona.
Steve Jessop

Soluzione fantastica e intelligente. +1!
Linuxios

7
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

Qui hai gli ID di tutti i sistemi operativi. Per me non ha alcun senso provare a fare qualcosa che dipende dal sistema operativo senza controllare questo sistema. È come comprare pantaloni verdi ma senza guardarli.


13
Ma l'interrogante dice esplicitamente without resorting to trying to detect the platform...
Frédéric Hamidi

1
@Linuxios: fino Linux vendor o Apple decide che non vogliono seguire lo standard POSIX . Questo tipo di codifica è notoriamente difficile da mantenere.
Fred Foo

2
@larsmans: Linux e Mac OS X fanno seguire lo standard . Il tuo collegamento è del 1997. È FreeBSD quello dietro.
dreamlax

3
@Linuxios: No, non è [meglio]. Se vuoi davvero fare i controlli della piattaforma, usa autoconf o uno strumento simile. Controllare il prototipo effettivo piuttosto che fare supposizioni che a un certo punto falliranno e falliranno per l'utente.
Michał Górny

2
@ MichałGórny: buon punto. Francamente, dovrei uscire da questa domanda. Non mi sembra di poter contribuire in alcun modo.
Linuxios

1

Hai indicato che l'utilizzo della tua funzione wrapper è accettabile. Sembri anche disposto a convivere con gli avvertimenti.

Quindi, invece di scrivere il tuo wrapper in C ++, scrivilo in C, dove riceverai un avviso solo su alcuni sistemi:

// my_iconv.h

#if __cpluscplus
extern "C" {
#endif

size_t my_iconv( iconv_t cd, char **restrict inbuf, ?* etc... */);


#if __cpluscplus
}
#endif



// my_iconv.c
#include <iconv.h>
#include "my_iconv.h"

size_t my_iconv( iconv_t cd, char **inbuf, ?* etc... */)
{
    return iconv( cd, 
                inbuf /* will generate a warning on FreeBSD */,
                /* etc... */
                );
}

1

Che ne dite di

static void Test(char **)
{
}

int main(void)
{
    const char *t="foo";
    Test(const_cast<char**>(&t));
    return 0;
}

EDIT: ovviamente, il "senza rilevare la piattaforma" è un po 'un problema. Oops :-(

EDIT 2: ok, forse una versione migliorata?

static void Test(char **)
{
}

struct Foo
{
    const char **t;

    operator char**() { return const_cast<char**>(t); }
    operator const char**() { return t; }

    Foo(const char* s) : t(&s) { }
};

int main(void)
{
    Test(Foo("foo"));
    return 0;
}

Il problema con questo è che sull'altra piattaforma non verrà compilato (cioè se la funzione prende un const char**fallirà)
David Rodríguez - dribeas

1

Che dire:

#include <cstddef>
using std::size_t;

// test harness, these definitions aren't part of the solution
#ifdef CONST_ICONV
    // other parameters removed for tediousness
    size_t iconv(const char **inbuf) { return 0; }
#else
    // other parameters removed for tediousness
    size_t iconv(char **inbuf) { return 0; }
#endif

// solution
template <typename T>
size_t myconv_helper(size_t (*system_iconv)(T **), char **inbuf) {
    return system_iconv((T**)inbuf); // sledgehammer cast
}

size_t myconv(char **inbuf) {
    return myconv_helper(iconv, inbuf);
}

// usage
int main() {
    char *foo = 0;
    myconv(&foo);
}

Penso che questo violi il rigoroso aliasing in C ++ 03, ma non in C ++ 11 perché in C ++ 11 const char**e char**sono i cosiddetti "tipi simili". Non eviterai quella violazione dell'aliasing rigoroso se non creando un const char*, impostalo uguale a *foo, chiama iconvcon un puntatore al temporaneo, quindi copia il risultato *foodopo a const_cast:

template <typename T>
size_t myconv_helper(size_t (*system_iconv)(T **), char **inbuf) {
    T *tmpbuf;
    tmpbuf = *inbuf;
    size_t result = system_iconv(&tmpbuf);
    *inbuf = const_cast<char*>(tmpbuf);
    return result;
}

Questo è al sicuro dal punto di vista della correttezza const, perché tutto iconvfa coninbuf è incrementare il puntatore memorizzato in esso. Quindi stiamo "eliminando const" da un puntatore derivato da un puntatore che non era const quando l'abbiamo visto per la prima volta.

Potremmo anche scrivere un sovraccarico di myconve myconv_helperche prendono const char **inbufe pasticciano le cose nell'altra direzione, in modo che il chiamante possa scegliere se passare in a const char**o a char**. Che probabilmente iconvavrebbe dovuto dare al chiamante in primo luogo in C ++, ma ovviamente l'interfaccia viene semplicemente copiata da C dove non c'è sovraccarico di funzioni.


Il codice "super pedanteria" non è necessario. Su GCC4.7 con uno stdlibc ++ corrente, è necessario questo per compilare.
Konrad Rudolph

1

Aggiornamento: ora vedo che è possibile gestirlo in C ++ senza autotools, ma lascio la soluzione autoconf per le persone che la cercano.

Quello che stai cercando è iconv.m4che sia installato dal pacchetto gettext.

AFAICS è solo:

AM_ICONV

in configure.ac, e dovrebbe rilevare il prototipo corretto.

Quindi, nel codice che usi:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif

usa la specializzazione del modello per questo. vedi sopra.
Alex

1
Grazie! Uso già gli autotools e questo sembra essere il modo standard per aggirare il problema, quindi dovrebbe essere perfetto! Sfortunatamente non sono stato in grado di ottenere autoconf per trovare il file iconv.m4 (e non sembra esistere su OS X, che ha una vecchia versione di autotools), quindi non sono riuscito a farlo funzionare in modo portabile . Cercare su Google mostra che molte persone hanno problemi con questa macro. Oh, autotools!
ridiculous_fish

Penso di avere un brutto trucco ma non rischioso nella mia risposta. Tuttavia, se stai già utilizzando autoconf e se la configurazione necessaria esiste sulle piattaforme a cui tieni, non c'è alcun motivo reale per non usarlo ...
Steve Jessop

Sul mio sistema quel file .m4 è installato dal gettextpacchetto. Inoltre, è abbastanza comune per i pacchetti includere le macro usate nella m4/directory e avere ACLOCAL_AMFLAGS = -I m4in Makefile.am. Penso che l'autopoint lo copi anche in quella directory per impostazione predefinita.
Michał Górny

0

Sono in ritardo a questa festa ma comunque, ecco la mia soluzione:

// This is here because some compilers (Sun CC) think that there is a
// difference if the typedefs are not in an extern "C" block.
extern "C"
{
//! SUSv3 iconv() type.
typedef size_t (& iconv_func_type_1) (iconv_t cd, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft); 


//! GNU iconv() type.
typedef size_t (& iconv_func_type_2) (iconv_t cd, const char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft);
} // extern "C"

//...

size_t
call_iconv (iconv_func_type_1 iconv_func, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft)
{
    return iconv_func (handle, inbuf, inbytesleft, outbuf, outbytesleft);
}

size_t
call_iconv (iconv_func_type_2 iconv_func, char * * inbuf,
    size_t * inbytesleft, char * * outbuf, size_t * outbytesleft)
{
    return iconv_func (handle, const_cast<const char * *>(inbuf),
        inbytesleft, outbuf, outbytesleft);
}

size_t
do_iconv (char * * inbuf, size_t * inbytesleft, char * * outbuf,
    size_t * outbytesleft)
{
    return call_iconv (iconv, inbuf, inbytesleft, outbuf, outbytesleft);
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.