我如何可移植地调用一些C ++函数,在某些平台上使用char **,而在其他平台上使用const char **?

在我的Linux(和OS X)机器上, iconv()函数具有这个原型:

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

而在FreeBSD上看起来像这样:

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

我想我的C ++代码在两个平台上构build。 使用C编译器,传递char**作为const char**参数(反之亦然)通常会发出纯粹的警告; 然而在C ++中这是一个致命的错误。 所以如果我传递一个char** ,它将不会在BSD上编译,如果我传递一个const char** ,它将不能在Linux / OS X上编译。我怎样才能编写在两个编译的代码,而不诉诸试图检测平台?

我有一个(失败的)想法是提供一个本地原型来覆盖头部提供的任何东西:

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

这样做是因为iconv需要C链接,并且你不能在一个函数中放置extern "C" (为什么不呢?)

我想到的最好的工作思路是投射函数指针本身:

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

但这有可能掩盖其他更严重的错误。

如果你想要的只是对一些常量问题视而不见,那么你可以使用模糊转换的转换,即使char **和const char **可互操作:

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

然后在程序中:

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

无论iconv需求的第二个参数如何,sloppy()都需要一个char**或一个const char*并将其转换为char**或者一个const char*

更新:改为使用const_cast并调用马虎不是一个强制转换。

您可以通过检查声明函数的签名来消除两个声明之间的歧义。 以下是检查参数types所需模板的基本示例。 这很容易被普遍化(或者你可以使用Boost的函数特征),但是这足以certificate你的特定问题的解决scheme:

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

下面是一个演示行为的例子:

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

一旦你可以检测到参数types的限定,你可以编写两个调用iconv包装函数:一个用char const**参数调用iconv ,另一个用char**参数调用iconv

因为应该避免函数模板专业化,所以我们使用类模板来进行专业化。 请注意,我们也使每个调用者都是一个函数模板,以确保只有我们使用的特化被实例化。 如果编译器试图为错误的特化生成代码,将会出错。

然后我们用call_iconv来包装这些用法,就像直接调用iconv一样简单。 以下是显示如何编写的一般模式:

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

(后面这个逻辑可以被清理和泛化,我试图使每一个部分都清楚,希望能够使它更清楚地表明它的工作原理。)

您可以使用以下内容:

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

你可以通过const char** ,在Linux / OSX上它将通过模板函数,在FreeBSD上它将直接转到iconv

缺点:它会允许像iconv(foo, 2.5)这样的调用iconv(foo, 2.5)这会使编译器无限循环。

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

这里你有所有操作系统的ID。 对我来说,没有任何意义可以尝试做一些依赖于操作系统的东西而不检查这个系统。 这就像买绿色长裤,但没有看着他们。

你已经指出使用你自己的包装函数是可以接受的。 你似乎也愿意忍受警告。

所以,不要用C ++来编写你的包装器,而应该用C语言编写,在这里你只会在某些系统上得到警告:

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

怎么样

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

编辑:当然,“没有检测到平台”是有点问题。 糟糕:-(

编辑2:好吧,改进版本,也许?

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

关于什么:

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

我认为这在C ++ 03中违反了严格的别名,但在C ++ 11中却没有,因为在C ++ 11中, const char**char**是所谓的“类似types”。 除了通过创build一个const char* ,将其设置为等于*foo ,您不会避免严格别名的违反。使用指向临时对象的指针调用iconv ,然后在const_cast之后将结果复制回*foo

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

这对于常量正确性的POV来说是安全的,因为所有iconvinbuf都会增加存储在其中的指针。 所以当我们第一次看到它的时候,我们从一个非const的指针派生的指针“抛弃const”。

我们也可以编写一个myconvmyconv_helper的重载myconv ,它接受const char **inbuf并在另一个方向上弄乱事情,这样调用者可以select是否传入一个const char**char** 。 可以说iconv本来应该给调用者在C + +,当然接口只是从C复制,没有函数重载。

更新:现在我看到可以在不使用自动工具的情况下使用C ++来处理它,但是我正在将autoconf解决scheme留给正在寻找它的人使用。

你正在寻找的是由gettext包安装的iconv.m4

AFAICS只是:

 AM_ICONV 

在configure.ac中,它应该检测正确的原型。

然后,在你使用的代码中:

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

我迟到了这个派对但是,这里是我的解决办法:

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