На моих машинах с Linux (и OS X) iconv()
функция имеет этот прототип:
size_t iconv (iconv_t, char **inbuf...
а на FreeBSD это выглядит так:
size_t iconv (iconv_t, const char **inbuf...
Я бы хотел, чтобы мой код C ++ работал на обеих платформах. В компиляторах C передача a 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);
но это может замаскировать другие, более серьезные ошибки.
источник
iconv
требует,inbuf
чтобы объект не был константным.iconv
безconst
: svnweb.freebsd.org/base/stable/9/include/...Ответы:
Если вы хотите просто закрыть глаза на некоторые проблемы с константой, вы можете использовать преобразование, которое стирает различие, то есть делает 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**
или aconst char*
и преобразует его в achar**
или aconst char*
, независимо от того, что требует второй параметр iconv.ОБНОВЛЕНИЕ: изменено использование const_cast и вызов sloppy not a as cast.
источник
sloppy<char**>()
инициализатор прямо там.(char**)&in
если вы сначала не создадите typedef дляchar**
.Вы можете устранить неоднозначность между двумя объявлениями, проверив подпись объявленной функции. Вот базовый пример шаблонов, необходимых для проверки типа параметра. Это можно легко обобщить (или вы можете использовать свойства функции 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**
аргументом.Поскольку следует избегать специализации шаблонов функций, мы используем шаблон класса для выполнения специализации. Обратите внимание, что мы также делаем каждого из вызывающих шаблонов функцией, чтобы гарантировать, что создается только та специализация, которую мы используем. Если компилятор попытается сгенерировать код для неправильной специализации, вы получите ошибки.
Затем мы оборачиваем их использование символом,
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 */); }
(Эту последнюю логику можно очистить и обобщить; я попытался сделать каждую ее часть явной, чтобы, надеюсь, прояснить, как она работает.)
источник
decltype
требуется C ++ 11.#ifdef
проверки платформы, у вас будет 30 с лишним строк кода :) Хороший подход (хотя в последние несколько дней я беспокоился, глядя на вопросы о том, что люди, которые не действительно понимают, что они делают, начали использовать SFINAE в качестве золотого молотка ... не в вашем случае, но я боюсь, что код станет более сложным и трудным в обслуживании ...)Вы можете использовать следующее:
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)
которые заставят компилятор бесконечно повторяться.источник
const_cast
необходимо переместить в объект,add_or_remove_const
который копается в,T**
чтобы определить,T
есть ли ,const
и добавить или удалить квалификацию, если это необходимо. Это все равно (намного) проще, чем решение, которое я продемонстрировал. Приложив немного усилий, можно также заставить это решение работать безconst_cast
(то есть, используя локальную переменную в вашемiconv
).iconv
является константой, неT
выводится какconst char**
, что означает, что параметрinbuf
имеет типconst T
, который естьconst char **const
, а вызовiconv
в шаблоне просто вызывает сам себя? Однако, как говорит Джеймс, с соответствующей модификацией типаT
этот трюк является основой того, что действительно работает.#ifdef __linux__ ... // linux code goes here. #elif __FreeBSD__ ... // FreeBSD code goes here. #endif
Здесь у вас есть идентификаторы всех операционных систем. Для меня нет никакого смысла пытаться делать что-то, что зависит от операционной системы, без проверки этой системы. Это как покупать зеленые брюки, но не смотреть на них.
источник
without resorting to trying to detect the platform
...Вы указали, что использование вашей собственной функции-оболочки допустимо. Вы также, кажется, готовы жить с предупреждениями.
Итак, вместо того, чтобы писать свою оболочку на 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; }
источник
const char**
она не сработает)Что о:
#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**
есть так называемые «похожие типы». Вы не сможете избежать этого нарушения строгого псевдонима, кроме как создаваяconst char*
, устанавливая его равным*foo
, вызываяiconv
с указателем на временное, а затем копируя результат обратно*foo
после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; }
Это безопасно с точки зрения константной корректности, потому что все
iconv
работает сinbuf
это увеличивает указатель, хранящийся в нем. Итак, мы «отбрасываем константу» из указателя, полученного из указателя, который не был константой, когда мы впервые его увидели.Мы также могли бы написать перегрузку
myconv
и,myconv_helper
которая беретconst char **inbuf
и портит вещи в другом направлении, чтобы у вызывающей стороны был выбор, передать ли ей aconst char**
или achar**
. Что, возможно,iconv
должно было быть предоставлено вызывающей стороне в первую очередь в C ++, но, конечно, интерфейс просто скопирован с C, где нет перегрузки функций.источник
Обновление: теперь я вижу, что с этим можно справиться на C ++ без autotools, но я оставляю решение autoconf для людей, которые его ищут.
То, что вы ищете,
iconv.m4
устанавливается пакетом gettext.AFAICS это просто:
в configure.ac, и он должен определить правильный прототип.
Затем в коде, который вы используете:
#ifdef ICONV_CONST // const char** #else // char** #endif
источник
gettext
пакетом. Кроме того, довольно часто пакеты включают используемые макросы вm4/
каталог и содержатACLOCAL_AMFLAGS = -I m4
файлыMakefile.am
. Я думаю, что autopoint даже копирует его в этот каталог по умолчанию.Я опаздываю на эту вечеринку, но все же вот мое решение:
// 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); }
источник