Wie kann ich eine C ++ - Funktion portabel aufrufen, die auf einigen Plattformen ein Zeichen ** und auf anderen ein const char ** verwendet?

91

Auf meinen Linux- (und OS X-) Computern hat die iconv()Funktion diesen Prototyp:

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

auf FreeBSD sieht es so aus:

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

Ich möchte, dass mein C ++ - Code auf beiden Plattformen erstellt wird. Bei C-Compilern gibt das Übergeben von a char**für einen const char**Parameter (oder umgekehrt) normalerweise nur eine Warnung aus. In C ++ ist dies jedoch ein schwerwiegender Fehler. Wenn ich also a übergebe char**, wird es unter BSD nicht kompiliert, und wenn ich a übergebe const char**, wird es unter Linux / OS X nicht kompiliert. Wie kann ich Code schreiben, der auf beiden kompiliert wird, ohne zu versuchen, die Plattform zu erkennen?

Eine (fehlgeschlagene) Idee war, einen lokalen Prototyp bereitzustellen, der alle vom Header bereitgestellten überschreibt:

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

Dies schlägt fehl, weil iconveine C-Verknüpfung erforderlich ist und Sie keine extern "C"Funktion einfügen können (warum nicht?)

Die beste Arbeitsidee, die ich mir ausgedacht habe, ist, den Funktionszeiger selbst umzuwandeln:

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

Dies kann jedoch andere, schwerwiegendere Fehler maskieren.

lächerlich_fisch
quelle
31
Eine verdammt gute Frage für deine erste SO. :)
Almo
24
Protokolliere einen Fehler bei FreeBSD. Für die POSIX-Implementierung von iconvmuss inbufnon-const sein.
Dreamlax
3
Das Casting dieser Funktion ist nicht portabel.
Jonathan Grynspan
2
@dreamlax: Das Einreichen eines Fehlerberichts hat wahrscheinlich keine Auswirkungen. Die aktuelle Version von FreeBSD hat offenbar schon iconvohne const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo
2
@larsmans: Das ist gut zu wissen! Ich habe noch nie FreeBSD verwendet, aber es ist gut zu wissen, dass die neueste Version den neuesten Standard unterstützt.
Dreamlax

Antworten:

57

Wenn Sie nur ein Auge auf einige konstante Probleme werfen möchten, können Sie eine Konvertierung verwenden, die die Unterscheidung verwischt, dh char ** und const char ** interoperabel macht:

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); }
};

Dann später im Programm:

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

sloppy () nimmt a char**oder a const char*und konvertiert es in a char**oder a const char*, unabhängig davon, was der zweite Parameter von iconv verlangt.

UPDATE: geändert, um const_cast zu verwenden und schlampig aufzurufen, nicht als Besetzung.

Nordischer Mainframe
quelle
Dies funktioniert recht gut und scheint sicher und unkompliziert zu sein, ohne dass C ++ 11 erforderlich ist. Ich gehe damit! Vielen Dank!
lächerlich_fisch
2
Wie ich in meiner Antwort gesagt, ich denke , das strenges Aliasing in C ++ verletzt 03, so dass in diesem Sinne ist es nicht erforderlich C ++ 11. Ich könnte mich jedoch irren, wenn jemand dies verteidigen will.
Steve Jessop
1
Bitte ermutigen Sie keine C-artigen Casts in C ++. Wenn ich mich nicht irre, können Sie den sloppy<char**>()Initialisierer direkt dort aufrufen .
Michał Górny
Es ist natürlich immer noch die gleiche Operation wie bei einer Besetzung im C-Stil, jedoch unter Verwendung der alternativen C ++ - Syntax. Ich denke, es könnte die Leser davon abhalten, in anderen Situationen Casts im C-Stil zu verwenden. Zum Beispiel würde die C ++ - Syntax für die Besetzung nur funktionieren, (char**)&inwenn Sie zuerst ein typedef für erstellen char**.
Steve Jessop
Guter Hack. Der Vollständigkeit halber könnten Sie dies wahrscheinlich so machen, dass entweder (a) immer ein const char * const * verwendet wird, vorausgesetzt, die Variable soll nicht geändert werden soll, oder (b) sie durch zwei beliebige Typen parametrisiert und zwischen ihnen const umgewandelt wird.
Jack V.
33

Sie können zwischen den beiden Deklarationen unterscheiden, indem Sie die Signatur der deklarierten Funktion überprüfen. Hier ist ein grundlegendes Beispiel für die Vorlagen, die zum Überprüfen des Parametertyps erforderlich sind. Dies könnte leicht verallgemeinert werden (oder Sie könnten die Funktionsmerkmale von Boost verwenden), aber dies reicht aus, um eine Lösung für Ihr spezifisches Problem zu demonstrieren:

#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 };
};

Hier ist ein Beispiel, das das Verhalten demonstriert:

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;
}

Sobald Sie die Qualifikation des Parametertyps erkannt haben, können Sie zwei Wrapper-Funktionen schreiben, die aufrufen iconv: eine, die iconvmit einem char const**Argument aufruft, und eine, die iconvmit einem char**Argument aufruft .

Da die Spezialisierung von Funktionsvorlagen vermieden werden sollte, verwenden wir eine Klassenvorlage, um die Spezialisierung durchzuführen. Beachten Sie, dass wir jeden Aufrufer auch zu einer Funktionsvorlage machen, um sicherzustellen, dass nur die von uns verwendete Spezialisierung instanziiert wird. Wenn der Compiler versucht, Code für die falsche Spezialisierung zu generieren, werden Fehler angezeigt.

Wir schließen diese dann mit einem ab call_iconv, um das Aufrufen so einfach wie das iconvdirekte Aufrufen zu machen . Das Folgende ist ein allgemeines Muster, das zeigt, wie dies geschrieben werden kann:

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 */);
}

(Diese letztere Logik könnte bereinigt und verallgemeinert werden. Ich habe versucht, jedes Stück explizit zu machen, um hoffentlich klarer zu machen, wie es funktioniert.)

James McNellis
quelle
3
Schöne Magie dort. :) Ich würde upvoten, weil es so aussieht, als würde es die Frage beantworten, aber ich habe nicht überprüft, ob es funktioniert, und ich kenne nicht genug Hardcore-C ++, um zu wissen, ob es funktioniert, indem ich es mir nur ansehe. :)
Almo
7
Hinweis: decltypeerfordert C ++ 11.
Michał Górny
1
+1 Lol ... um zu vermeiden #ifdef, dass Sie nach der Plattform suchen, erhalten Sie 30 ungerade Codezeilen :) Netter Ansatz (obwohl ich mir in den letzten Tagen Sorgen gemacht habe, Fragen zu SO zu stellen, die Leute nicht haben Ich verstehe wirklich, was sie tun. Ich habe angefangen, SFINAE als goldenen Hammer zu verwenden ... nicht Ihr Fall, aber ich befürchte, dass der Code komplexer und schwer zu pflegen sein wird ...)
David Rodríguez - Dribeas
11
@ DavidRodríguez-dribeas: :-) Ich folge einfach der goldenen Regel des modernen C ++: Wenn etwas keine Vorlage ist, fragen Sie sich: "Warum ist das keine Vorlage?" dann mache es zu einer Vorlage.
James McNellis
1
[Bevor jemand diesen letzten Kommentar zu ernst nimmt: Es ist ein Witz. Art von ...]
James McNellis
11

Sie können Folgendes verwenden:

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);
}

Sie können übergeben const char**und unter Linux / OSX wird es die Vorlagenfunktion durchlaufen und unter FreeBSD wird es direkt zu gehen iconv.

Nachteil: Es ermöglicht Aufrufe, mit iconv(foo, 2.5)denen der Compiler unendlich oft wiederholt wird.

Krizz
quelle
2
Nett! Ich denke, diese Lösung hat Potenzial: Ich mag die Verwendung der Überlastungsauflösung, um die Vorlage nur auszuwählen, wenn die Funktion nicht genau übereinstimmt. Um zu arbeiten, const_castmüsste der jedoch in einen verschoben werden add_or_remove_const, der sich in den eingreift T**, um festzustellen, ob dies der Fall Tist, constund gegebenenfalls eine Qualifikation hinzufügen oder entfernen. Dies wäre immer noch (weitaus) einfacher als die Lösung, die ich demonstriert habe. Mit ein wenig Arbeit ist es möglicherweise auch möglich, diese Lösung ohne das zu verwenden const_cast(dh indem Sie eine lokale Variable in Ihrem verwenden iconv).
James McNellis
Habe ich etwas verpasst In dem Fall, in dem der Real iconvnicht const ist, wird nicht Tals abgeleitet const char**, was bedeutet, dass der Parameter inbufden Typ hat const T, der ist const char **const, und der Aufruf von iconvin der Vorlage sich selbst aufruft? Wie James jedoch sagt, ist Tdieser Trick mit einer geeigneten Modifikation des Typs die Grundlage für etwas, das funktioniert.
Steve Jessop
Tolle, clevere Lösung. +1!
Linuxios
7
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

Hier haben Sie IDs aller Betriebssysteme. Für mich hat es keinen Sinn, etwas zu tun, was vom Betriebssystem abhängt, ohne dieses System zu überprüfen. Es ist, als würde man eine grüne Hose kaufen, ohne sie anzusehen.

Blut
quelle
13
Aber der Fragesteller sagt ausdrücklich without resorting to trying to detect the platform...
Frédéric Hamidi
1
@Linuxios: bis Linux - Anbieter oder Apple entscheiden , sie haben die folgen wollen POSIX - Standard . Diese Art der Codierung ist bekanntermaßen schwer zu pflegen.
Fred Foo
2
@larsmans: Linux und Mac OS X tun dem Standard folgen . Ihr Link stammt aus dem Jahr 1997. Es ist FreeBSD, das dahinter steckt.
Dreamlax
3
@Linuxios: Nein, es ist nicht [besser]. Wenn Sie wirklich Plattformprüfungen durchführen möchten, verwenden Sie autoconf oder ein ähnliches Tool. Überprüfen Sie den tatsächlichen Prototyp, anstatt Annahmen zu treffen, die irgendwann fehlschlagen und beim Benutzer fehlschlagen.
Michał Górny
2
@ MichałGórny: Guter Punkt. Ehrlich gesagt sollte ich einfach aus dieser Frage herauskommen. Ich scheine nichts dazu beitragen zu können.
Linuxios
1

Sie haben angegeben, dass die Verwendung Ihrer eigenen Wrapper-Funktion akzeptabel ist. Sie scheinen auch bereit zu sein, mit Warnungen zu leben.

Schreiben Sie Ihren Wrapper also nicht in C ++, sondern in C, wo Sie nur auf einigen Systemen eine Warnung erhalten:

// 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... */
                );
}
Michael Burr
quelle
1

Wie wäre es mit

static void Test(char **)
{
}

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

EDIT: Natürlich ist das "ohne die Plattform zu erkennen" ein kleines Problem. Hoppla :-(

EDIT 2: ok, verbesserte Version vielleicht?

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;
}
Christian Stieber
quelle
Das Problem dabei ist, dass es auf der anderen Plattform nicht kompiliert wird (dh wenn die Funktion eine benötigt const char**,
schlägt
1

Wie wäre es mit:

#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);
}

Ich denke , das strenge Aliasing in C ++ verletzt 03, aber nicht in C ++ 11 , da in C ++ 11 const char**und char**sind so genannte "ähnliche Typen". Sie werden diese Verletzung des strengen Aliasing nicht vermeiden, außer indem Sie ein erstellen const char*, es auf setzen *foo, iconvmit einem Zeiger auf das temporäre Element aufrufen und das Ergebnis *foonach a zurückkopieren 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;
}

Dies ist sicher vor dem POV der Konstantenkorrektheit, weil alles damit zu iconvtun hatinbuf Zuwachs ist der Zeiger in ihm gespeichert. Wir "werfen const weg" von einem Zeiger, der von einem Zeiger abgeleitet wurde, der nicht const war, als wir ihn zum ersten Mal sahen.

Wir könnten auch eine Überlastung der schreiben myconvund myconv_helperdas nimmt const char **inbufund Verwirrungen Dinge über in der anderen Richtung, so dass der Anrufer die Wahl hat , ob in einem zu übergeben const char**oder ein char**. Welches wohliconv dem Aufrufer in C ++ eigentlich hätte geben sollen, aber natürlich wird die Schnittstelle nur von C kopiert, wo es keine Funktionsüberladung gibt.

Steve Jessop
quelle
Der "Super-Pedanterie" -Code ist nicht erforderlich. Unter GCC4.7 mit einem aktuellen stdlibc ++ benötigen Sie dies zum Kompilieren.
Konrad Rudolph
1

Update: Jetzt sehe ich, dass es möglich ist, es in C ++ ohne Autotools zu handhaben, aber ich lasse die Autoconf-Lösung für Leute, die danach suchen.

Was Sie suchen, ist iconv.m4 das, was von gettext package installiert wird.

AFAICS es ist nur:

AM_ICONV

in configure.ac, und es sollte den richtigen Prototyp erkennen.

Dann in dem Code, den Sie verwenden:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
Michał Górny
quelle
Verwenden Sie dazu die Template-Spezialisierung. siehe oben.
Alex
1
Vielen Dank! Ich verwende bereits Autotools, und dies scheint die Standardmethode zu sein, um das Problem zu umgehen. Es sollte also perfekt sein! Leider konnte ich autoconf nicht dazu bringen, die Datei iconv.m4 zu finden (und sie scheint unter OS X mit einer alten Version von Autotools nicht zu existieren), sodass ich sie nicht portabel zum Laufen bringen konnte . Das Googeln zeigt, dass viele Leute Probleme mit diesem Makro haben. Oh, Autotools!
lächerlich_fisch
Ich denke, ich habe einen hässlichen, aber nicht riskanten Hack in meiner Antwort. Wenn Sie Autoconf bereits verwenden und die erforderliche Konfiguration auf den Plattformen vorhanden ist, die Sie interessieren, gibt es keinen wirklichen Grund, dies nicht zu verwenden ...
Steve Jessop
Auf meinem System wird diese .m4-Datei per gettextPaket installiert . Auch ist es durchaus üblich , für Verpackungen verwendet Makros im aufzunehmen m4/Verzeichnis und haben ACLOCAL_AMFLAGS = -I m4in Makefile.am. Ich denke, Autopoint kopiert es sogar standardmäßig in dieses Verzeichnis.
Michał Górny
0

Ich bin zu spät zu dieser Party, aber hier ist meine Lösung:

// 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);
}
wilx
quelle