Bazı platformlarda char ** ve diğerlerinde const char ** alan bir C ++ işlevini taşınabilir bir şekilde nasıl çağırabilirim?


91

Linux (ve OS X) makinelerimde, iconv()işlev şu prototipe sahiptir:

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

FreeBSD'de şu şekilde görünür:

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

C ++ kodumun her iki platformda da oluşturulmasını istiyorum. C derleyicileriyle, char**bir const char**parametre için (veya tam tersi) geçmek tipik olarak yalnızca bir uyarı verir; ancak C ++ 'da bu ölümcül bir hatadır. Yani eğer char**a'yı geçersem, BSD'de derlenmez ve eğer bir const char**geçersem Linux / OS X'de derlenmez. Platformu tespit etmeye çalışmadan her ikisinde de derleyen kod yazabilirim?

Aklımdaki (başarısız) bir fikir, başlık tarafından sağlananları geçersiz kılan yerel bir prototip sağlamaktı:

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

Bu başarısız olur çünkü iconvC bağlantısına ihtiyaç vardır ve extern "C"bir işlevin içine koyamazsınız (neden olmasın?)

Bulduğum en iyi çalışma fikri, işlev işaretçisini kendisinin atmasıdır:

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

ancak bu daha ciddi hataları maskeleme potansiyeline sahiptir.


31
SO'daki ilkiniz için harika bir soru. :)
Almo

24
FreeBSD'de bir hata kaydedin. POSIX uygulaması, sabit olmayan iconvolmasını gerektirir inbuf.
dreamlax

3
İşlevi bu şekilde yayınlamak taşınabilir değildir.
Jonathan Grynspan

2
@dreamlax: Bir hata raporu göndermenin bir etkisi olma ihtimali düşüktür; FreeBSD'nin şu anki sürümünün şu özelliklere sahip iconvolmadığı görülüyor const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo

2
@larsmans: Bunu bilmek güzel! Hiç FreeBSD kullanmadım ancak en son sürümün en son standardı desteklediğini bilmek güzel.
dreamlax

Yanıtlar:


57

İstediğiniz şey yalnızca bazı sabit sorunları görmezden gelmekse, o zaman ayrımı bulanıklaştıran, yani char ** ve const char ** ile birlikte çalışabilir hale getiren bir dönüştürme kullanabilirsiniz:

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

Daha sonra programda:

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

sloppy (), a char**veya a'yı alır const char*ve iconv'nin ikinci parametresi ne olursa olsun bunu a char**veya a'ya dönüştürür const char*.

GÜNCELLEME: const_cast kullanmak için değiştirildi ve döküm olarak değil özensiz çağrısı.


Bu oldukça iyi çalışıyor ve C ++ 11 gerektirmeden güvenli ve anlaşılır görünüyor. Ben onunla gidiyorum! Teşekkürler!
ridiculous_fish

2
Benim cevap söylediğim gibi, bu yüzden bu anlamda, bu C ++ 03 katı yumuşatma ihlal ettiğini düşündüğünüz gelmez ++ 11 C gerektirir. Yine de, bunu savunmak isteyen varsa yanılıyor olabilirim.
Steve Jessop

1
Lütfen C ++ 'da C tarzı yayınları teşvik etmeyin; yanılıyorsam, sloppy<char**>()doğrudan oradan başlatıcıyı arayabilirsiniz .
Michał Górny

Elbette, yine de C tarzı bir döküm ile aynı işlem, ancak alternatif C ++ sözdizimini kullanıyor. Sanırım okuyucuları diğer durumlarda C tarzı yayınlar kullanmaktan caydırabilir. Örneğin, C ++ sözdizimi, (char**)&inönce typedef for char**.
Steve Jessop

İyi hack. Tamlık için, muhtemelen bunu (a) her zaman bir const char * const * alarak, değişkenin değiştirilmeyeceğini varsayarak ya da (b) herhangi iki tür tarafından parametreleştirip aralarındaki const cast yaparak yapabilirsiniz.
Jack V.

33

Bildirilen işlevin imzasını inceleyerek iki bildirim arasındaki belirsizliği ortadan kaldırabilirsiniz. Parametre türünü incelemek için gereken şablonların temel bir örneğini burada bulabilirsiniz. Bu kolayca genelleştirilebilir (veya Boost'un işlev özelliklerini kullanabilirsiniz), ancak bu, özel probleminize bir çözüm göstermek için yeterlidir:

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

İşte davranışı gösteren bir örnek:

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

Parametre türü niteliğini algılayabilir sonra, çağrı iki sarıcı işlevleri yazabilirsiniz iconv: birini o çağrıları iconvbir ile char const**argüman ve bir o çağrıları iconvbir ile char**argüman.

İşlev şablonu uzmanlaşmasından kaçınılması gerektiğinden, uzmanlaşmayı yapmak için bir sınıf şablonu kullanıyoruz. Ayrıca, yalnızca kullandığımız uzmanlığın somutlaştırılmasını sağlamak için her invokeri bir işlev şablonu yaptığımızı unutmayın. Derleyici yanlış uzmanlık için kod üretmeye çalışırsa, hatalar alırsınız.

Daha sonra, call_iconvbunu iconvdoğrudan aramak kadar basit bir şekilde çağırmak için bunların kullanımını a ile sarıyoruz . Aşağıda bunun nasıl yazılabileceğini gösteren genel bir kalıp verilmiştir:

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

(Bu son mantık temizlenebilir ve genelleştirilebilir; umarım nasıl çalıştığını daha net hale getirmek için her bir parçasını açık hale getirmeye çalıştım.)


3
Orada güzel sihir. :) Soruyu yanıtlıyor gibi gözüktüğü için oy verirdim, ancak çalıştığını doğrulamadım ve sadece bakarak işe yarayıp yaramadığını anlayacak kadar sert C ++ bilmiyorum. :)
Almo

7
Not olarak: decltypeC ++ 11 gerektirir.
Michał Górny

1
+1 Lol ... bu yüzden #ifdefplatformu kontrol etmekten kaçınmak için 30 garip kod satırı elde edersiniz :) Güzel yaklaşım (son birkaç gündür SO'daki sorulara bakarken endişelenmeme rağmen ne yaptıklarını gerçekten anlayın SFINAE'yi altın bir çekiç olarak kullanmaya başladık ... sizin durumunuz değil, ancak kodun daha karmaşık ve bakımı zor hale gelmesinden korkuyorum ...)
David Rodríguez - dribeas

11
@ DavidRod Rodríguez-dribeas: :-) Ben sadece modern C ++ 'nın altın kuralını uyguluyorum: eğer bir şey şablon değilse, kendinize sorun, "Bu neden bir şablon değil?" sonra onu bir şablon yapın.
James McNellis

1
[Kimse bu son yorumu fazla ciddiye almadan: bu bir şaka. Sırala ...]
James McNellis

11

Aşağıdakileri kullanabilirsiniz:

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

Geçebilirsiniz const char**ve Linux / OSX üzerinde şablon işlevinden geçecek ve FreeBSD'de doğrudan gidecektir iconv.

Dezavantajı: iconv(foo, 2.5)Derleyiciyi sonsuz yinelemeye sokacak gibi çağrılara izin verecektir.


2
Güzel! Bence bu çözümün potansiyeli var: Şablonu seçmek için aşırı yük çözünürlüğünün kullanılmasını, yalnızca işlev tam olarak eşleşmediğinde seviyorum. İşin için, ancak, const_castbir taşındı gerekecektir add_or_remove_constiçine kazar o T**olmadığını tespit etmek Tolduğunu constve uygun olarak eklemek veya kaldır yeterlilik. Bu yine de gösterdiğim çözümden (çok) daha basit olacaktır. Biraz çalışmayla, bu çözümün onsuz çalışması da mümkün olabilir const_cast(yani, sizde yerel bir değişken kullanarak iconv).
James McNellis

Bir şey mi kaçırdım? iconvGerçeğin const olmadığı durumda , olarak Tçıkarılmaz const char**, bu, parametrenin inbuftürü olduğu anlamına gelir const T, yani const char **const, ve iconvşablondaki çağrının yalnızca kendisini çağırdığı anlamına gelir? James'in dediği gibi, tipte uygun bir değişiklikle Tbu numara, işe yarayan bir şeyin temelini oluşturur.
Steve Jessop

Harika, akıllı çözüm. +1!
Linuxios

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

Burada tüm işletim sistemlerinin kimliklerine sahipsiniz. Benim için bu sistemi kontrol etmeden işletim sistemine bağlı bir şey yapmayı denemenin bir anlamı yok. Yeşil pantolon almaya benziyor ama onlara bakmadan.


14
Ama soruyu soran açıkça diyor ki without resorting to trying to detect the platform...
Frédéric Hamidi

1
@Linuxios: Linux satıcıları veya Apple onlar karar verene kadar do takip etmek istiyorum POSIX standardını . Bu tür bir kodlamanın korunması herkesin bildiği gibi zordur.
Fred Foo

2
@larsmans: Linux ve Mac OS X do standart takip . Bağlantınız 1997'den. Arkasında FreeBSD var.
dreamlax

3
@Linuxios: Hayır, [daha iyi] değil. Gerçekten platform kontrolleri yapmak istiyorsanız, autoconf veya benzer bir araç kullanın. Bir noktada başarısız olacak varsayımlar yapmak yerine gerçek prototipi kontrol edin ve kullanıcı üzerinde başarısız olacaktır.
Michał Górny

2
@ MichałGórny: İyi bir nokta. Açıkçası, bu sorudan çıkmalıyım. Görünüşe göre ona hiçbir şey katacak durumda değilim.
Linuxios

1

Kendi sarmalayıcı işlevinizi kullanmanın kabul edilebilir olduğunu belirttiniz. Ayrıca uyarılarla yaşamaya istekli görünüyorsun.

Bu nedenle, sarmalayıcınızı C ++ 'da yazmak yerine, yalnızca bazı sistemlerde bir uyarı alacağınız C dilinde yazın:

// 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

Ne dersin

static void Test(char **)
{
}

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

DÜZENLEME: Tabii ki, "platformu tespit etmeden" biraz problemdir. Hata :-(

DÜZENLEME 2: tamam, geliştirilmiş sürüm, belki?

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

Bununla ilgili sorun şu ki, diğer platformda derlenmeyecek (yani işlev bir alırsa const char**başarısız olacaktır)
David Rodríguez - dribeas

1

Ne dersin:

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

Bunun C ++ 03'te katı örtüşme ihlal ettiğini düşünüyorum, ancak C ++ 11'de değil çünkü C ++ 11'de const char**ve char**sözde "benzer türler" olarak adlandırılıyor. Bu katı örtüşme ihlalini, a oluşturmak const char*, eşit olarak ayarlamak , geçici bir işaretçi ile *fooaramak iconvve ardından sonucu a'dan *foosonra geri kopyalamak dışında engelleyemezsiniz 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;
}

Bu sabit doğruluk bakış açısına göre güvenlidir, çünkü tüm iconvyaptığı, inbufiçinde depolanan işaretçiyi artırmaktır. Yani ilk gördüğümüzde const olmayan bir göstericiden türetilen bir göstericiden "const uzağa atıyoruz".

Ayrıca, bir aşırı yük yazabiliriz myconvve myconv_helperbu const char **inbuf, işleri diğer yönde alıp karıştırır , böylece arayan kişi a const char**veya a geçme seçeneğine sahip olur char**. Muhtemelen iconvarayan kişiye C ++ 'da ilk etapta verilmiş olmalıydı, ancak elbette arayüz, işlev aşırı yüklemesinin olmadığı C'den kopyalandı.


"Süper bilgiçlik" kodu gereksizdir. GCC4.7'de güncel bir stdlibc ++ ile derlemek için buna ihtiyacınız vardır .
Konrad Rudolph

1

Güncelleme: Artık bunu otomatik araçlar olmadan C ++ 'da kullanmanın mümkün olduğunu görüyorum, ancak bunu arayan insanlar için autoconf çözümünü bırakıyorum.

Aradığınız şey iconv.m4gettext paketi tarafından kuruludur.

AFAICS sadece:

AM_ICONV

configure.ac içinde ve doğru prototipi algılaması gerekir.

Ardından, kullandığınız kodda:

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

bunun için şablon uzmanlığını kullanın. yukarıyı görmek.
Alexander Oh

1
Teşekkürler! Zaten otomatik araçlar kullanıyorum ve bu, sorunu çözmenin standart yolu gibi görünüyor, bu yüzden mükemmel olmalı! Ne yazık ki, iconv.m4 dosyasını bulabilmek için autoconf'u alamadım (ve bu, autotools'un eski bir sürümüne sahip olan OS X'te mevcut görünmüyor), bu yüzden onu taşınabilir bir şekilde çalıştırmayı başaramadım . Google'da dolaşmak, birçok insanın bu makroyla sorun yaşadığını gösteriyor. Oh, otomatik araçlar!
ridiculous_fish

Cevabımda çirkin ama risksiz bir hack olduğunu düşünüyorum. Zaten autoconf'u kullanıyorsanız gerekli yapılandırma önemsediğiniz platformlarda varsa Yine, ve bu kullanmak için hiçbir gerçek nedeni var ...
Steve Jessop

Sistemimde bu .m4 dosyası gettextpaket tarafından kurulur . Paketler halinde kullanılan makro dahil etmek için de, oldukça yaygındır m4/dizin ve sahip ACLOCAL_AMFLAGS = -I m4de Makefile.am. Hatta autopoint varsayılan olarak bu dizine kopyaladığını düşünüyorum.
Michał Górny

0

Bu partiye geç kaldım ama yine de işte çözümüm:

// 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);
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.