Bagaimana saya bisa memanggil fungsi C ++ yang mengambil karakter ** pada beberapa platform dan karakter ** pada yang lain?

91

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 iconvmembutuhkan 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.

ridiculous_fish
sumber
31
Pertanyaan yang luar biasa untuk pertama Anda di SO. :)
Almo
24
Catat bug di FreeBSD. Implementasi POSIX iconvmembutuhkan inbufmenjadi non-const.
dreamlax
3
Transmisi fungsi seperti itu tidak portabel.
Jonathan Grynspan
2
@dreamlax: mengirimkan laporan bug sepertinya tidak akan berpengaruh; versi FreeBSD saat ini tampaknya sudah iconvtanpa const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo
2
@larsmans: Itu bagus untuk diketahui! Saya belum pernah menggunakan FreeBSD tetapi bagus untuk mengetahui versi terbaru mendukung standar terbaru.
dreamlax

Jawaban:

57

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 a const char*dan mengubahnya menjadi a char**atau a const char*, apa pun yang diminta oleh parameter kedua dari iconv.

UPDATE: diubah untuk menggunakan const_cast dan memanggil ceroboh bukan sebagai cast.

Mainframe Nordik
sumber
Ini berfungsi cukup baik, dan tampaknya aman serta langsung tanpa memerlukan C ++ 11. Saya akan melakukannya! Terima kasih!
ridiculous_fish
2
Seperti yang saya katakan dalam jawaban saya, saya pikir ini melanggar aliasing ketat di C ++ 03, jadi dalam arti itu memang membutuhkan C ++ 11. Saya mungkin salah, jika ada yang ingin mempertahankan ini.
Steve Jessop
1
Harap jangan dorong pemeran gaya-C di C ++; kecuali saya salah, Anda bisa memanggil sloppy<char**>()penginisialisasi langsung di sana.
Michał Górny
Ini masih operasi yang sama seperti cast gaya-C, tetapi menggunakan sintaks C ++ alternatif. Saya kira itu mungkin membuat pembaca enggan menggunakan gaya C dalam situasi lain. Misalnya sintaks C ++ tidak akan berfungsi untuk cast (char**)&inkecuali Anda terlebih dahulu membuat typedef untuk char**.
Steve Jessop
Retas yang bagus. Untuk kelengkapan, Anda mungkin bisa membuat ini baik (a) selalu mengambil const char * const *, dengan asumsi variabel tidak seharusnya diubah atau (b) parameterisasi oleh dua jenis dan membuatnya konstan di antara mereka.
Jack V.
33

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 memanggil iconvdengan char const**argumen dan satu lagi yang memanggil iconvdengan char**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_iconvmembuat panggilan ini sesederhana menelepon iconvsecara 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.)

James McNellis
sumber
3
Sihir yang bagus di sana. :) Saya akan memberi suara positif karena sepertinya itu menjawab pertanyaan, tetapi saya belum memverifikasi bahwa itu berfungsi, dan saya tidak cukup tahu C ++ hardcore untuk mengetahui apakah itu berfungsi hanya dengan melihatnya. :)
Almo
7
Sebagai catatan: decltypemembutuhkan C ++ 11.
Michał Górny
1
+1 Lol ... jadi untuk menghindari #ifdefpengecekan 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 ...)
David Rodríguez - dribeas
11
@ DavidRodríguez-dribeas: :-) Saya hanya mengikuti aturan emas C ++ modern: jika sesuatu bukan template, tanyakan pada diri Anda, "mengapa ini bukan template?" lalu jadikan sebagai template.
James McNellis
1
[Sebelum ada yang menganggap komentar terakhir itu terlalu serius: itu lelucon. Semacam ...]
James McNellis
11

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 ke iconv.

Kekurangan: itu akan memungkinkan panggilan seperti iconv(foo, 2.5)yang akan menempatkan compiler dalam pengulangan tak terbatas.

Krizz
sumber
2
Bagus! Saya rasa solusi ini memiliki potensi: Saya suka penggunaan resolusi yang berlebihan untuk memilih template hanya jika fungsinya tidak sama persis. Untuk bekerja, bagaimanapun, const_castakan perlu dipindahkan ke add_or_remove_constyang menggali ke dalam T**untuk mendeteksi apakah Tada constdan 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 tanpa const_cast(yaitu, dengan menggunakan variabel lokal di Anda iconv).
James McNellis
Apakah saya melewatkan sesuatu? Dalam kasus di mana real iconvadalah non-const, tidak Tdisimpulkan sebagai const char**, yang berarti bahwa parameter inbufmemiliki tipe const T, yang mana const char **const, dan panggilan ke iconvdalam template hanya memanggil dirinya sendiri? Seperti yang dikatakan James, dengan modifikasi yang sesuai dengan jenisnya T, trik ini adalah dasar dari sesuatu yang berhasil.
Steve Jessop
Solusi yang luar biasa dan cerdas. +1!
Linuxios
7
#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.

Darah
sumber
14
Tapi penanya secara eksplisit mengatakan without resorting to trying to detect the platform...
Frédéric Hamidi
1
@Linuxios: sampai Linux vendor atau Apple memutuskan mereka tidak ingin mengikuti standar POSIX . Pengkodean semacam ini sangat sulit dipertahankan.
Fred Foo
2
@larsmans: Linux dan Mac OS X lakukan mengikuti standar . Tautan Anda berasal dari tahun 1997. FreeBSD-lah yang tertinggal.
dreamlax
3
@ Linuxios: Tidak, ini tidak [lebih baik]. Jika Anda benar-benar ingin melakukan pemeriksaan platform, gunakan autoconf atau alat serupa. Periksa prototipe sebenarnya daripada melakukan asumsi yang akan gagal di beberapa titik, dan akan gagal pada pengguna.
Michał Górny
2
@ MichałGórny: Poin yang bagus. Terus terang, saya harus keluar dari pertanyaan ini. Saya sepertinya tidak dapat memberikan kontribusi apa pun untuk itu.
Linuxios
1

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

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;
}
Christian Stieber
sumber
Masalah dengan itu adalah bahwa di platform lain itu tidak akan dikompilasi (yaitu jika fungsi mengambil const char**itu akan gagal)
David Rodríguez - dribeas
1

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**dan char**disebut "tipe serupa". Anda tidak akan menghindari pelanggaran aliasing ketat selain dengan membuat const char*, setel sama dengan *foo, panggil iconvdengan pointer ke sementara, lalu salin hasilnya kembali ke *foosetelah 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;
}

Ini aman dari POV of const-correctness, karena semua yang iconvdilakukan inbufadalah 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 myconvdan myconv_helperyang mengambil const char **inbufdan mengacaukan hal-hal ke arah lain, sehingga penelepon memiliki pilihan apakah akan lulus di a const char**atau a char**. Yang bisa dibilang iconvseharusnya diberikan kepada pemanggil di tempat pertama di C ++, tetapi tentu saja antarmuka hanya disalin dari C di mana tidak ada fungsi yang berlebihan.

Steve Jessop
sumber
Kode "super-pedantry" tidak diperlukan. Di GCC4.7 dengan stdlibc ++ saat ini, Anda memerlukan ini untuk mengompilasi.
Konrad Rudolph
1

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.m4yang diinstal oleh paket gettext.

AFAICS hanya saja:

AM_ICONV

di configure.ac, dan itu harus mendeteksi prototipe yang benar.

Kemudian, di kode yang Anda gunakan:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
Michał Górny
sumber
gunakan spesialisasi template untuk itu. Lihat di atas.
Alexander Oh
1
Terima kasih! Saya sudah menggunakan autotools, dan ini tampaknya menjadi cara standar untuk mengatasi masalah ini, jadi ini seharusnya sempurna! Sayangnya saya tidak bisa mendapatkan autoconf untuk menemukan file iconv.m4 (dan sepertinya tidak ada di OS X, yang memiliki autotools versi kuno), jadi saya tidak bisa membuatnya bekerja secara portabel. . Mencari-cari di Google menunjukkan bahwa banyak orang mengalami masalah dengan makro ini. Oh, autotools!
ridiculous_fish
Saya pikir saya memiliki peretasan yang jelek tetapi tidak berisiko dalam jawaban saya. Namun, jika Anda sudah menggunakan autoconf, dan jika konfigurasi yang diperlukan ada pada platform yang Anda pedulikan, tidak ada alasan nyata untuk tidak menggunakan ini ...
Steve Jessop
Di sistem saya, file .m4 diinstal oleh gettextpaket. Selain itu, cukup umum untuk paket menyertakan makro bekas dalam m4/direktori dan memiliki ACLOCAL_AMFLAGS = -I m4in Makefile.am. Saya pikir titik otomatis bahkan menyalinnya ke direktori itu secara default.
Michał Górny
0

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