Na moich komputerach z systemem Linux (i OS X) iconv()
funkcja ma następujący prototyp:
size_t iconv (iconv_t, char **inbuf...
podczas gdy we FreeBSD wygląda to tak:
size_t iconv (iconv_t, const char **inbuf...
Chciałbym, aby mój kod C ++ był budowany na obu platformach. W kompilatorach C, przekazanie parametru char**
a const char**
(lub odwrotnie) zazwyczaj generuje zwykłe ostrzeżenie; jednak w C ++ jest to błąd krytyczny. Więc jeśli zdam char**
, nie będzie kompilował się na BSD, a jeśli zdam, const char**
nie będzie się kompilował w systemie Linux / OS X. Jak mogę napisać kod, który kompiluje się na obu, bez uciekania się do próby wykrycia platformy?
Jeden (nieudany) pomysł, jaki miałem, polegał na dostarczeniu lokalnego prototypu, który zastąpi wszystkie podane w nagłówku:
void myfunc(void) {
size_t iconv (iconv_t, char **inbuf);
iconv(foo, ptr);
}
To się nie udaje, ponieważ iconv
wymaga połączenia C i nie możesz wstawić extern "C"
funkcji (dlaczego nie?)
Najlepszym działającym pomysłem jest rzutowanie samego wskaźnika funkcji:
typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);
ale może to maskować inne, poważniejsze błędy.
źródło
iconv
wymaga,inbuf
aby element nie był stałą.iconv
bezconst
: svnweb.freebsd.org/base/stable/9/include/...Odpowiedzi:
Jeśli chcesz po prostu przymknąć oko na niektóre problemy ze stałymi, możesz użyć konwersji, która zaciera różnicę, tj. Sprawia, że char ** i const char ** są interoperacyjne:
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); } };
Następnie w programie:
iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);
sloppy () przyjmuje a
char**
lub aconst char*
i konwertuje je na achar**
lub aconst char*
, niezależnie od tego, czego wymaga drugi parametr iconv.UPDATE: zmieniono na używanie const_cast i wywołanie niechlujstwa, a nie jako cast.
źródło
sloppy<char**>()
bezpośrednio wywołać inicjalizator.(char**)&in
chyba że najpierw utworzysz typedef dlachar**
.Możesz rozróżnić dwie deklaracje, sprawdzając podpis zadeklarowanej funkcji. Oto podstawowy przykład szablonów wymaganych do sprawdzenia typu parametru. Można to łatwo uogólnić (lub użyć cech funkcji Boost), ale to wystarczy, aby zademonstrować rozwiązanie konkretnego problemu:
#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 }; };
Oto przykład demonstrujący zachowanie:
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; }
Po wykryciu kwalifikacji typu parametru możesz napisać dwie funkcje opakowujące, które wywołują
iconv
: jedną wywołującąiconv
zchar const**
argumentem i jedną, która wywołujeiconv
zchar**
argumentem.Ponieważ należy unikać specjalizacji szablonów funkcji, do specjalizacji używamy szablonu klasy. Zauważ, że każdy wywoływacz jest również szablonem funkcji, aby mieć pewność, że utworzona zostanie tylko ta specjalizacja, której używamy. Jeśli kompilator spróbuje wygenerować kod dla złej specjalizacji, otrzymasz błędy.
Następnie zawijamy ich użycie,
call_iconv
aby wywołanie tego było tak proste, jakiconv
bezpośrednie wywołanie . Poniżej znajduje się ogólny wzorzec pokazujący, jak można to zapisać: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 */); }
(Tę drugą logikę można by wyczyścić i uogólnić; starałem się, aby każdy jej element był wyraźny, aby, miejmy nadzieję, wyjaśnić, jak to działa).
źródło
decltype
wymaga C ++ 11.#ifdef
sprawdzania platformy, otrzymujesz 30 nieparzystych linii kodu :) Jednak miłe podejście (chociaż w ciągu ostatnich kilku dni martwiłem się, patrząc na pytania dotyczące SO, że ludzie, którzy nie naprawdę rozumieją, co robią, zaczęli używać SFINAE jako złotego młotka ... nie w twoim przypadku, ale obawiam się, że kod stanie się bardziej złożony i trudny do utrzymania ...)Możesz użyć następujących:
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); }
Możesz przekazać
const char**
i na Linux / OSX przejdzie przez funkcję szablonu, a we FreeBSD przejdzie bezpośrednio doiconv
.Wada: zezwoli na wywołania takie jak,
iconv(foo, 2.5)
które ustawią kompilator w nieskończonej powtarzalności.źródło
const_cast
należałoby przenieść go do elementu,add_or_remove_const
który zagłębia się w,T**
aby wykryć, czyT
jest,const
i odpowiednio dodać lub usunąć kwalifikację. Byłoby to (o wiele) prostsze niż rozwiązanie, które pokazałem. Przy odrobinie pracy może być również możliwe sprawienie, aby to rozwiązanie działało bezconst_cast
(tj. Używając zmiennej lokalnej w twoimiconv
).iconv
nie jest stała , nie jestT
wydedukowana jakoconst char**
, co oznacza, że parametrinbuf
ma typconst T
, czyli jestconst char **const
, a wywołanieiconv
w szablonie po prostu wywołuje siebie? Jak jednak mówi James, z odpowiednią modyfikacją typuT
ta sztuczka jest podstawą czegoś, co działa.#ifdef __linux__ ... // linux code goes here. #elif __FreeBSD__ ... // FreeBSD code goes here. #endif
Tutaj masz identyfikatory wszystkich systemów operacyjnych. Dla mnie nie ma sensu próbować robić czegoś, co zależy od systemu operacyjnego, bez sprawdzania tego systemu. To jak kupowanie zielonych spodni, ale bez patrzenia na nie.
źródło
without resorting to trying to detect the platform
...Wskazałeś, że używanie własnej funkcji opakowującej jest dopuszczalne. Wydaje się, że chcesz żyć z ostrzeżeniami.
Więc zamiast pisać swój wrapper w C ++, napisz go w C, gdzie otrzymasz ostrzeżenie tylko w niektórych systemach:
// 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... */ ); }
źródło
Co powiesz na
static void Test(char **) { } int main(void) { const char *t="foo"; Test(const_cast<char**>(&t)); return 0; }
EDYCJA: oczywiście „bez wykrywania platformy” jest trochę problemem. Ups :-(
EDYCJA 2: ok, może ulepszona wersja?
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; }
źródło
const char**
to się nie powiedzie)Co powiesz na:
#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); }
Myślę, że narusza to ścisłe aliasy w C ++ 03, ale nie w C ++ 11, ponieważ w C ++ 11
const char**
ichar**
są to tak zwane „podobne typy”. Nie zamierzasz uniknąć tego naruszenia ścisłego aliasingu, poza utworzeniem aconst char*
, ustawieniem go na równe*foo
, wywołaniemiconv
ze wskaźnikiem do tymczasowego, a następnie skopiowaniem wyniku z powrotem*foo
poconst_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; }
Jest to bezpieczne od punktu widzenia stałej poprawności, ponieważ wszystko
iconv
ma związek zinbuf
to inkrementacja przechowywanego w nim wskaźnika. Więc „odrzucamy stałą” ze wskaźnika wywodzącego się ze wskaźnika, który nie był stałą, kiedy pierwszy raz go zobaczyliśmy.Moglibyśmy również napisać przeciążenie
myconv
i,myconv_helper
które zabierająconst char **inbuf
i komplikują rzeczy w innym kierunku, tak aby dzwoniący miał wybór, czy przekazać aconst char**
lub achar**
. Co prawdopodobnieiconv
powinno było dać wywołującemu na pierwszym miejscu w C ++, ale oczywiście interfejs jest po prostu kopiowany z C, gdzie nie ma przeciążenia funkcji.źródło
Aktualizacja: teraz widzę, że da się to obsłużyć w C ++ bez narzędzi autotools, jednak rozwiązanie autoconf zostawiam osobom, które go szukają.
To, czego szukasz,
iconv.m4
jest instalowane przez pakiet gettext.AFAICS to po prostu:
w configure.ac i powinien wykryć poprawny prototyp.
Następnie w kodzie, którego używasz:
#ifdef ICONV_CONST // const char** #else // char** #endif
źródło
gettext
pakiet. Ponadto dość często zdarza się, że pakiety zawierają używane makra wm4/
katalogu i mająACLOCAL_AMFLAGS = -I m4
wMakefile.am
. Myślę, że autopoint nawet domyślnie kopiuje go do tego katalogu.Spóźniłem się na tę imprezę, ale oto moje rozwiązanie:
// 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); }
źródło