如何可移植地调用一个 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 + + 代码能在这两个平台上构建。对于 C 编译器,为 const char**参数传递 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);

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

5563 次浏览
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

Here you have ids of all operating systems. For me it doesn't have any point to try doing something what depends on operating system without checking this system. It's like buying green trousers but without looking at them.

How about

static void Test(char **)
{
}


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

EDIT: of course, the "without detecting the platform" is a bit of a problem. Oops :-(

EDIT 2: ok, improved version, maybe?

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

You've indicated that using your own wrapper function is acceptable. You also seem to be willing to live with warnings.

So, instead of writing your wrapper in C++, write it in C, where you'll only get a warning on some systems:

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

You can disambiguate between the two declarations by inspecting the signature of the declared function. Here's a basic example of the templates required to inspect the parameter type. This could easily be generalized (or you could use Boost's function traits), but this is sufficient to demonstrate a solution for your specific problem:

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

Here's an example that demonstrates the behavior:

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

Once you can detect the qualification of the parameter type, you can write two wrapper functions that call iconv: one that calls iconv with a char const** argument and one that calls iconv with a char** argument.

Because function template specialization should be avoided, we use a class template to do the specialization. Note that we also make each of the invokers a function template, to ensure that only the specialization we use is instantiated. If the compiler tries to generate code for the wrong specialization, you'll get errors.

We then wrap usage of these with a call_iconv to make calling this as simple as calling iconv directly. The following is a general pattern showing how this can be written:

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

(This latter logic could be cleaned up and generalized; I've tried to make each piece of it explicit to hopefully make it clearer how it works.)

Update: now I see that it is possible to handle it in C++ without autotools, yet I'm leaving the autoconf solution for people looking for it.

What you're looking for is iconv.m4 which is installed by gettext package.

AFAICS it's just:

AM_ICONV

in configure.ac, and it should detect the correct prototype.

Then, in the code you use:

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

You can use the following:

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

You can pass const char** and on Linux/OSX it will go through the template function and on FreeBSD it will go directly to iconv.

Drawback: it will allow calls like iconv(foo, 2.5) which will put compiler in infinite recurrence.

If what you want is just to turn a blind eye to some const issues, then you can use a conversion which blurs the distinction, i.e. makes char** and const char** interoperable:

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

Then later in the program:

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

sloppy() takes a char** or a const char* and converts it to a char** or a const char*, whatever the second parameter of iconv demands.

UPDATE: changed to use const_cast and call sloppy not a as cast.

What about:

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

I think this violates strict aliasing in C++03, but not in C++11 because in C++11 const char** and char** are so-called "similar types". You aren't going to avoid that violation of strict aliasing other than by creating a const char*, set it equal to *foo, call iconv with a pointer to the temporary, then copy the result back to *foo after a 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;
}

This is safe from the POV of const-correctness, because all iconv does with inbuf is increment the pointer stored in it. So we're "casting away const" from a pointer derived from a pointer that was non-const when we first saw it.

We could also write an overload of myconv and myconv_helper that take const char **inbuf and messes things about in the other direction, so that the caller has the choice whether to pass in a const char** or a char**. Which arguably iconv should have given to the caller in the first place in C++, but of course the interface is just copied from C where there's no function overloading.

I am late to this party but still, here is my solution:

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