Di mesin Linux (dan OS X) saya, iconv()
fungsinya memiliki prototipe ini:
size_t iconv (iconv_t, char **inbuf...
sedangkan di FreeBSD terlihat seperti ini:
size_t iconv (iconv_t, const char **inbuf...
Saya ingin kode C ++ saya dibuat di kedua platform. Dengan kompiler C, melewati char**
untuk const char**
parameter (atau sebaliknya) biasanya memancarkan peringatan belaka; namun di C ++ ini adalah kesalahan fatal. Jadi, jika saya lulus a char**
, itu tidak akan dapat dikompilasi di BSD, dan jika saya meneruskannya const char**
tidak akan dapat dikompilasi di Linux / OS X. Bagaimana saya bisa menulis kode yang mengkompilasi keduanya, tanpa harus mencoba mendeteksi platformnya?
Satu ide (gagal) yang saya miliki adalah menyediakan prototipe lokal yang menggantikan apa pun yang disediakan oleh tajuk:
void myfunc(void) {
size_t iconv (iconv_t, char **inbuf);
iconv(foo, ptr);
}
Ini gagal karena iconv
membutuhkan tautan C, dan Anda tidak dapat memasukkan extern "C"
fungsi (mengapa tidak?)
Ide kerja terbaik yang saya temukan adalah dengan menggunakan penunjuk fungsi itu sendiri:
typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);
tapi ini berpotensi menutupi kesalahan lain yang lebih serius.
sumber
iconv
membutuhkaninbuf
menjadi non-const.iconv
tanpaconst
: svnweb.freebsd.org/base/stable/9/include/…Jawaban:
Jika yang Anda inginkan hanyalah menutup mata terhadap beberapa masalah const, Anda dapat menggunakan konversi yang mengaburkan perbedaannya, yaitu membuat char ** dan const char ** dapat dioperasikan:
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); } };
Kemudian di program nanti:
iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);
sloppy () mengambil a
char**
atau aconst char*
dan mengubahnya menjadi achar**
atau aconst char*
, apa pun yang diminta oleh parameter kedua dari iconv.UPDATE: diubah untuk menggunakan const_cast dan memanggil ceroboh bukan sebagai cast.
sumber
sloppy<char**>()
penginisialisasi langsung di sana.(char**)&in
kecuali Anda terlebih dahulu membuat typedef untukchar**
.Anda dapat membedakan kedua deklarasi tersebut dengan memeriksa tanda tangan dari fungsi yang dideklarasikan. Berikut adalah contoh dasar template yang diperlukan untuk memeriksa tipe parameter. Ini dapat dengan mudah digeneralisasikan (atau Anda dapat menggunakan ciri-ciri fungsi Boost), tetapi ini cukup untuk menunjukkan solusi untuk masalah spesifik Anda:
#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 }; };
Berikut adalah contoh yang mendemonstrasikan perilaku tersebut:
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; }
Setelah Anda bisa mendeteksi kualifikasi tipe parameter, Anda bisa menulis dua fungsi pembungkus yang memanggil
iconv
: satu yang memanggiliconv
denganchar const**
argumen dan satu lagi yang memanggiliconv
denganchar**
argumen.Karena spesialisasi template fungsi harus dihindari, kami menggunakan template kelas untuk melakukan spesialisasi. Perhatikan bahwa kita juga menjadikan setiap invokers sebagai template fungsi, untuk memastikan bahwa hanya spesialisasi yang kita gunakan yang dipakai. Jika kompilator mencoba membuat kode untuk spesialisasi yang salah, Anda akan mendapatkan kesalahan.
Kami kemudian membungkus penggunaan ini dengan
call_iconv
membuat panggilan ini sesederhana meneleponiconv
secara langsung. Berikut adalah pola umum yang menunjukkan bagaimana ini dapat ditulis: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 */); }
(Logika yang terakhir ini dapat dibersihkan dan digeneralisasikan; Saya telah mencoba membuat setiap bagiannya eksplisit agar lebih jelas cara kerjanya.)
sumber
decltype
membutuhkan C ++ 11.#ifdef
pengecekan platform, Anda akan mendapatkan 30 baris kode yang aneh :) Pendekatan yang bagus (meskipun saya khawatir dalam beberapa hari terakhir melihat pertanyaan tentang SO yang tidak dilakukan oleh orang-orang benar-benar mengerti apa yang mereka lakukan sudah mulai menggunakan SFINAE sebagai palu emas ... bukan kasus Anda, tapi saya khawatir kode akan menjadi lebih kompleks dan sulit dipertahankan ...)Anda dapat menggunakan berikut ini:
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); }
Anda dapat menggunakan
const char**
dan di Linux / OSX itu akan melalui fungsi template dan di FreeBSD itu akan langsung keiconv
.Kekurangan: itu akan memungkinkan panggilan seperti
iconv(foo, 2.5)
yang akan menempatkan compiler dalam pengulangan tak terbatas.sumber
const_cast
akan perlu dipindahkan keadd_or_remove_const
yang menggali ke dalamT**
untuk mendeteksi apakahT
adaconst
dan menambah atau menghapus kualifikasi yang sesuai. Ini masih (jauh) lebih mudah daripada solusi yang telah saya tunjukkan. Dengan sedikit usaha, dimungkinkan juga untuk membuat solusi ini bekerja tanpaconst_cast
(yaitu, dengan menggunakan variabel lokal di Andaiconv
).iconv
adalah non-const, tidakT
disimpulkan sebagaiconst char**
, yang berarti bahwa parameterinbuf
memiliki tipeconst T
, yang manaconst char **const
, dan panggilan keiconv
dalam template hanya memanggil dirinya sendiri? Seperti yang dikatakan James, dengan modifikasi yang sesuai dengan jenisnyaT
, trik ini adalah dasar dari sesuatu yang berhasil.#ifdef __linux__ ... // linux code goes here. #elif __FreeBSD__ ... // FreeBSD code goes here. #endif
Di sini Anda memiliki ID dari semua sistem operasi. Bagi saya tidak ada gunanya mencoba melakukan sesuatu yang bergantung pada sistem operasi tanpa memeriksa sistem ini. Ini seperti membeli celana panjang hijau tapi tanpa melihatnya.
sumber
without resorting to trying to detect the platform
...Anda telah menyatakan bahwa menggunakan fungsi pembungkus Anda sendiri dapat diterima. Anda juga tampaknya mau hidup dengan peringatan.
Jadi, daripada menulis pembungkus Anda dalam C ++, tulislah dalam C, di mana Anda hanya akan mendapatkan peringatan pada beberapa sistem:
// 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... */ ); }
sumber
Bagaimana tentang
static void Test(char **) { } int main(void) { const char *t="foo"; Test(const_cast<char**>(&t)); return 0; }
EDIT: tentu saja, "tanpa mendeteksi platform" adalah sedikit masalah. Ups :-(
EDIT 2: oke, versi yang ditingkatkan, mungkin?
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; }
sumber
const char**
itu akan gagal)Bagaimana dengan:
#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); }
Saya pikir ini melanggar aliasing ketat di C ++ 03, tetapi tidak di C ++ 11 karena di C ++ 11
const char**
danchar**
disebut "tipe serupa". Anda tidak akan menghindari pelanggaran aliasing ketat selain dengan membuatconst char*
, setel sama dengan*foo
, panggiliconv
dengan pointer ke sementara, lalu salin hasilnya kembali ke*foo
setelahconst_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; }
Ini aman dari POV of const-correctness, karena semua yang
iconv
dilakukaninbuf
adalah meningkatkan pointer yang disimpan di dalamnya. Jadi kita "membuang const" dari penunjuk yang diturunkan dari penunjuk yang bukan konstanta saat pertama kali melihatnya.Kami juga dapat menulis kelebihan
myconv
danmyconv_helper
yang mengambilconst char **inbuf
dan mengacaukan hal-hal ke arah lain, sehingga penelepon memiliki pilihan apakah akan lulus di aconst char**
atau achar**
. Yang bisa dibilangiconv
seharusnya diberikan kepada pemanggil di tempat pertama di C ++, tetapi tentu saja antarmuka hanya disalin dari C di mana tidak ada fungsi yang berlebihan.sumber
Pembaruan: sekarang saya melihat bahwa itu mungkin untuk menanganinya di C ++ tanpa autotools, namun saya meninggalkan solusi autoconf untuk orang yang mencarinya.
Apa yang Anda cari adalah
iconv.m4
yang diinstal oleh paket gettext.AFAICS hanya saja:
di configure.ac, dan itu harus mendeteksi prototipe yang benar.
Kemudian, di kode yang Anda gunakan:
#ifdef ICONV_CONST // const char** #else // char** #endif
sumber
gettext
paket. Selain itu, cukup umum untuk paket menyertakan makro bekas dalamm4/
direktori dan memilikiACLOCAL_AMFLAGS = -I m4
inMakefile.am
. Saya pikir titik otomatis bahkan menyalinnya ke direktori itu secara default.Saya terlambat ke pesta ini tetapi tetap saja, inilah solusi saya:
// 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); }
sumber