91

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.

pb2q
  • 58,613
  • 19
  • 146
  • 147
ridiculous_fish
  • 17,273
  • 1
  • 54
  • 61
  • 31
    Hell of a question for your first on SO. :) – Almo Jul 10 '12 at 20:37
  • 24
    Log a bug at FreeBSD. The POSIX implementation of [`iconv`](http://pubs.opengroup.org/onlinepubs/009695399/functions/iconv.html) requires the `inbuf` to be non-const. – dreamlax Jul 10 '12 at 20:41
  • 3
    Casting the function like that is non-portable. – Jonathan Grynspan Jul 10 '12 at 20:45
  • Maybe there's a compiler switch or a necessary define to make FreeBSD POSIX-compliant? – Niklas B. Jul 10 '12 at 20:47
  • 1
    Are you using autoconf and friends ? If so, you can have it generate a `config.h` which exposes a universal version (say the one taking `char**`) as is if it exists, and provides a wrapper if it does not. – Alexandre C. Jul 10 '12 at 20:52
  • 2
    @dreamlax: submitting a bug report is unlikely to have an effect; the current version of FreeBSD apparently already has `iconv` without the `const`: http://svnweb.freebsd.org/base/stable/9/include/iconv.h?revision=225736&view=markup – Fred Foo Jul 10 '12 at 20:53
  • 2
    @larsmans: That's good to know! I've never used FreeBSD but good to know the latest version supports the latest standard. – dreamlax Jul 10 '12 at 21:05
  • @ridiculous_fish: First question, and your on the newsletter! Nice! And 63 upvotes! That's impressive. Keep asking like this, and you'll be an asking legend. – Linuxios Jul 17 '12 at 18:23

9 Answers9

56

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.

Nordic Mainframe
  • 28,058
  • 10
  • 66
  • 83
  • This works quite well, and seems to be safe and straightforward without requiring C++11. I'm going with it! Thanks! – ridiculous_fish Jul 10 '12 at 22:20
  • 2
    As I said in my answer, I think this violates strict aliasing in C++03, so in that sense it *does* require C++11. I might be wrong though, if anyone wants to defend this. – Steve Jessop Jul 10 '12 at 23:09
  • 1
    Please do not encourage C-style casts in C++; unless I'm wrong, you could call `sloppy()` initializer directly there. – Michał Górny Jul 11 '12 at 07:21
  • It's still the same operation as a C-style cast of course, but using the alternative C++ syntax. I guess it might discourage readers from using C-style casts in other situations. For example the C++ syntax wouldn't work for the cast `(char**)&in` unless you first make a typedef for `char**`. – Steve Jessop Jul 11 '12 at 08:36
  • Good hack. For completeness, you could probably make this either (a) always taking a const char* const*, assuming the variable isn't supposed to be altered or (b) parameterising by any two types and making it const cast between them. – Jack V. Jul 11 '12 at 09:28
  • @Jack: reading the docs for `iconv`, it *does* modify `*inbuf` (to tell the caller how much of the buffer it has consumed). It doesn't modify `**inbuf`. So I think it would be pointless/dangerous to accept `const char* const*` in this particular case - no caller should ever try to supply it, but if they do it shouldn't succeed. That means you might want different versions of `sloppy` if you had to cope with other functions that like `iconv` had incompatible signatures on different implementations, depending what the function actually does. – Steve Jessop Jul 11 '12 at 10:50
  • Thank you. Then yeah, one of the others would be correct. (I guess const char** is logically correct, but char** is more convenient.) – Jack V. Jul 12 '12 at 08:47
  • Shouldn't it be `sloppy(&in)`? – Per Johansson Jul 14 '12 at 14:21
33

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

James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • 3
    Nice magic there. :) I'd upvote because it looks like it answers the question, but I haven't verified that it works, and I don't know enough hardcore C++ to know if it does just by looking at it. :) – Almo Jul 10 '12 at 20:54
  • 7
    As a note: `decltype` requires C++11. – Michał Górny Jul 10 '12 at 20:55
  • @James: The code to check is fine. How do you want to declare the pointer that is passed to iconf? `typedef const char ** value_type;` and declare the variable as `use_const::value_type x = "foobar";` ? – Alexander Oh Jul 10 '12 at 21:10
  • @Alex: I've never used this `iconv` function, so I'm not familiar with its usage. I've added an example that demonstrates how you can write two wrappers, one to call `iconv` with each of the signatures, and how to ensure that only the right one is instantiated and used. It's still a bit messy; I'll have time later this evening perhaps to provide a better implementation. – James McNellis Jul 10 '12 at 21:14
  • 1
    +1 Lol... so to avoid an `#ifdef` checking for the platform you end up with 30 odd lines of code :) Nice approach though (although I have been worrying in the last few days looking at questions on SO that people that don't really understand what they are doing have started using SFINAE as a golden hammer... not your case, but I fear code will become more complex and hard to maintain...) – David Rodríguez - dribeas Jul 10 '12 at 22:19
  • 11
    @DavidRodríguez-dribeas: :-) I just follow the golden rule of modern C++: if something isn't a template, ask yourself, "why isn't this a template?" then make it a template. – James McNellis Jul 10 '12 at 22:26
  • 1
    [Before anyone takes that last comment too seriously: it is a joke. Sort of...] – James McNellis Jul 10 '12 at 22:27
  • @JamesMcNellis: Would not the dispatch magic be easier using `std::enable_if` on overloads of `call_iconv` ? – Matthieu M. Jul 15 '12 at 16:03
  • @MatthieuM.: Depending on how the function is used (I am not at all familiar with this function), it'd probably be easiest to do this without templates and having two overloads that are selected based on the type of `&iconv` (i.e., two functions with a different first parameter type). Then, within the function, you can cast the arguments as required. I have been swamped for the last few days and haven't had the opportunity to come clean up this answer, which evolved from "hey, you can detect the difference using templates" to "and here's how you might do that, rather messily." – James McNellis Jul 15 '12 at 16:38
11

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.

Krizz
  • 11,362
  • 1
  • 30
  • 43
  • 2
    Nice! I think this solution has potential: I like the use of overload resolution to select the template only when the function is not an exact match. To work, however, the `const_cast` would need to be moved into an `add_or_remove_const` that digs into the `T**` to detect whether `T` is `const` and add or remove qualification as appropriate. This would still be (far) more straightforward than the solution I've demonstrated. With a bit of work, it might also be possible to make this solution work without the `const_cast` (i.e., by using a local variable in your `iconv`). – James McNellis Jul 10 '12 at 21:20
  • Have I missed something? In the case where the real `iconv` is non-const, isn't `T` deduced as `const char**`, which means that the parameter `inbuf` has type `const T`, which is `const char **const`, and the call to `iconv` in the template just calls itself? Like James says though, with a suitable modification to the type `T` this trick is the basis of something that does work. – Steve Jessop Jul 10 '12 at 22:36
7
#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.

Blood
  • 4,126
  • 3
  • 27
  • 37
  • 14
    But the questioner explicitly says `without resorting to trying to detect the platform`... – Frédéric Hamidi Jul 10 '12 at 20:37
  • @FrédéricHamidi: But checking the platform is going to be the most reliable way. – Linuxios Jul 10 '12 at 20:38
  • 1
    @Linuxios: until Linux vendors or Apple decide they *do* want to follow the [POSIX standard](http://pubs.opengroup.org/onlinepubs/7908799/xsh/iconv.html). This kind of coding is notoriously hard to maintain. – Fred Foo Jul 10 '12 at 20:39
  • @larsmans: It;s better than hacking with pointers and hacks that are liable to break/not follow the C standard. – Linuxios Jul 10 '12 at 20:40
  • 2
    @larsmans: Linux and Mac OS X [*do* follow the standard](http://pubs.opengroup.org/onlinepubs/009695399/functions/iconv.html). Your link is from 1997. It is FreeBSD that is behind. – dreamlax Jul 10 '12 at 20:45
  • @larsmans: See dreamlax's comment on the question. FreeBSD is the one not following POSIX. – Linuxios Jul 10 '12 at 20:45
  • @dreamlax: that's good to know. Thanks for the correction, I should have looked up the most recent version. – Fred Foo Jul 10 '12 at 20:46
  • 3
    @Linuxios: No, it's not [better]. If you really want to do platform checks, use autoconf or a similar tool. Check the actual prototype rather than doing assumptions which will fail at some point, and it will fail on user. – Michał Górny Jul 10 '12 at 20:47
  • 2
    @MichałGórny: Good point. Frankly, I should just get out of this question. I don't seem to be able to contribute anything to it. – Linuxios Jul 10 '12 at 20:52
  • It seems that, in FreeBSD 9, the `const` is already gone, meaning that this solution only "works" for older releases: http://svnweb.freebsd.org/base/stable/9/include/iconv.h?revision=225736&view=markup – Fred Foo Jul 10 '12 at 20:55
1

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;
}
Christian Stieber
  • 9,954
  • 24
  • 23
1

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... */
                );
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
1

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
Michał Górny
  • 18,713
  • 5
  • 53
  • 76
  • use template specialization for that. see above. – Alexander Oh Jul 10 '12 at 21:02
  • 1
    Thanks! I already use autotools, and this appears to be the standard way to work around the issue, so it ought to be perfect! Unfortunately I wasn't able to get autoconf to find the iconv.m4 file (and it doesn't seem to exist on OS X, which has an ancient version of autotools), so I wasn't able to get it to work portably. Googling around shows that lots of people have trouble with this macro. Oh, autotools! – ridiculous_fish Jul 10 '12 at 21:59
  • I think I have an ugly but non-risky hack in my answer. Still, if you're already using autoconf, and if the necessary config exists on the platforms you care about, there's no real reason not to use this... – Steve Jessop Jul 10 '12 at 23:03
  • On my system that .m4 file is installed by `gettext` package. Also, it is quite common for packages to include used macros in the `m4/` directory and have `ACLOCAL_AMFLAGS = -I m4` in `Makefile.am`. I think autopoint even copies it to that directory by default. – Michał Górny Jul 11 '12 at 07:26
1

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.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
0

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);
}
wilx
  • 17,697
  • 6
  • 59
  • 114