Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I portably call a C++ function that takes a char** on some platforms and a const char** on others?

On my Linux (and OS X) machines, the iconv() function has this prototype:

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

while on FreeBSD it looks like this:

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

I would like my C++ code to build on both platforms. With C compilers, passing a char** for a const char** parameter (or vice versa) typically emits a mere warning; however in C++ it's a fatal error. So if I pass a char**, it won't compile on BSD, and if I pass a const char** it won't compile on Linux / OS X. How can I write code that compiles on both, without resorting to trying to detect the platform?

One (failed) idea I had was to provide a local prototype that overrides any provided by the header:

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

This fails because iconv needs C linkage, and you cannot put extern "C" within a function (why not?)

The best working idea I've come up with is to cast the function pointer itself:

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

but this has the potential to mask other, more serious errors.

like image 384
ridiculous_fish Avatar asked Jul 10 '12 20:07

ridiculous_fish


4 Answers

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.

like image 156
Nordic Mainframe Avatar answered Nov 10 '22 21:11

Nordic Mainframe


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

like image 20
James McNellis Avatar answered Nov 10 '22 22:11

James McNellis


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.

like image 11
Krizz Avatar answered Nov 10 '22 22:11

Krizz


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

like image 7
Blood Avatar answered Nov 10 '22 22:11

Blood