Как я могу переносить функцию С++, которая принимает на своем компьютере char ** на некоторых платформах и const char ** на других?

На моих машинах Linux (и OS X) функция iconv() имеет этот прототип:

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

а во FreeBSD он выглядит так:

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

Я бы хотел, чтобы мой код на С++ работал на обеих платформах. С компиляторами C передача параметра char** для параметра const char** (или наоборот) обычно выдает простое предупреждение; однако в С++ это фатальная ошибка. Поэтому, если я передаю 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);

но это может маскировать другие, более серьезные ошибки.

Ответ 1

Если вы хотите просто закрывать глаза на некоторые проблемы с константой, вы можете использовать преобразование, которое размывает различие, т.е. совместимость 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);

sloppy() принимает a char** или const char* и преобразует его в char** или const char*, независимо от второго параметра iconv.

UPDATE: изменено на использование const_cast и вызов sloppy не как cast.

Ответ 2

Вы можете устранить двусмысленность между двумя объявлениями, проверив подпись объявленной функции. Вот базовый пример шаблонов, необходимых для проверки типа параметра. Это может быть легко обобщено (или вы можете использовать функции функции Boost), но этого достаточно, чтобы продемонстрировать решение для вашей конкретной проблемы:

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

Как только вы сможете определить квалификацию типа параметра, вы можете написать две функции-обертки, которые вызывают iconv: тот, который вызывает iconv с аргументом char const**, и тот, который вызывает iconv с char** аргумент.

Поскольку специализацию шаблона функций следует избегать, мы используем шаблон класса для специализации. Обратите внимание, что мы также делаем каждого из invokers шаблоном функции, чтобы гарантировать, что только специализированная работа, которую мы используем, создается. Если компилятор пытается сгенерировать код для неправильной специализации, вы получите ошибки.

Затем мы используем их с помощью 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 */);
}

(Эта последняя логика может быть очищена и обобщена, я попытался сделать каждую ее часть явной, чтобы надежно разъяснить, как она работает.)

Ответ 3

Вы можете использовать следующее:

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), который поместит компилятор в бесконечное повторение.

Ответ 4

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

Здесь есть идентификаторы всех операционных систем. Для меня нет смысла пытаться сделать что-то, что зависит от операционной системы, не проверяя эту систему. Это нравится покупать зеленые брюки, но не глядя на них.

Ответ 5

Как насчет

static void Test(char **)
{
}

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

EDIT: конечно, "без обнаружения платформы" является проблемой. К сожалению: - (

EDIT 2: ok, улучшенная версия, может быть?

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

Ответ 6

Вы указали, что использование вашей собственной функции-обертки приемлемо. Вы также, похоже, готовы жить с предупреждениями.

Итак, вместо написания вашей оболочки в С++ напишите ее на 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... */
                );
}

Ответ 7

Обновление: теперь я вижу, что это можно обрабатывать на С++ без autotools, но я оставляю решение autoconf для людей, которые его ищут.

То, что вы ищете, это iconv.m4, который устанавливается пакетом gettext.

АФИАКА просто:

AM_ICONV

в configure.ac, и он должен обнаружить правильный прототип.

Затем в коде, который вы используете:

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

Ответ 8

Как насчет:

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

Я думаю, что это нарушает строгий псевдоним в С++ 03, но не в С++ 11, потому что в С++ 11 const char** и char** есть так называемые "подобные типы". Вы не избежите этого нарушения строгих псевдонимов, кроме как создав const char*, установите его равным *foo, вызовите iconv указателем на временный, затем скопируйте результат обратно на *foo после 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;
}

Это безопасно от POV const-correctness, потому что все iconv с inbuf увеличивает указатель, хранящийся в нем. Таким образом, мы "отбрасываем const" из указателя, полученного из указателя, который был не const const, когда мы его впервые увидели.

Мы могли бы также написать перегрузку myconv и myconv_helper, которые принимают const char **inbuf и разбираются в другом направлении, так что у вызывающего есть выбор, следует ли передавать в const char** или char**. Что, возможно, iconv должно было дать вызывающему в первую очередь в С++, но, конечно, интерфейс просто скопирован с C, где нет перегрузки функций.

Ответ 9

Я опаздываю на эту вечеринку, но все же, вот мое решение:

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