Як я можу переносити функцію C ++, яка приймає char ** на одних платформах і const char ** на інших?


91

На моїх машинах Linux (і OS X) iconv()функція має такий прототип:

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

на FreeBSD це виглядає так:

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

Я хотів би, щоб мій код C ++ був побудований на обох платформах. З компіляторами C передача параметра char**для const char**параметра (або навпаки), як правило, видає лише попередження; однак у C ++ це фатальна помилка. Отже, якщо я передаю a 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);

але це може потенційно замаскувати інші, більш серйозні помилки.


31
Пекло питання для вашого першого на SO. :)
Almo,

24
Зареєструйте помилку на FreeBSD. Реалізація POSIX iconvвимагає, inbufщоб не було const.
dreamlax

3
Трансляція такої функції є непереносимою.
Джонатан Грінспан,

2
@dreamlax: подання звіту про помилку навряд чи матиме ефект; Поточна версія FreeBSD, мабуть, уже є iconvбез const: svnweb.freebsd.org/base/stable/9/include/…
Фред Фо

2
@larsmans: Це добре знати! Я ніколи не використовував FreeBSD, але добре знати, що остання версія підтримує найновіший стандарт.
dreamlax

Відповіді:


57

Якщо ви хочете просто закрити очі на деякі проблеми const, тоді ви можете скористатися перетворенням, яке розмиває відмінності, тобто робить 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**або a const char*і перетворює його в a char**або a const char*, що б не вимагав другий параметр iconv.

ОНОВЛЕННЯ: змінено на використання const_cast і виклик неакуратно, а не як приведення.


Це працює досить добре, і здається безпечним і простим, не вимагаючи C ++ 11. Я йду з цим! Дякую!
ridiculous_fish

2
Як я вже говорив в моїй обороні, я думаю , що це порушує строгу ступенчатость в C ++ 03, так що в цьому сенсі це дійсно вимагає C ++ 11. Однак я можу помилятися, якщо хтось хоче захистити це.
Steve Jessop

1
Будь ласка, не заохочуйте актори в стилі C на C ++; якщо я не помиляюся, ви можете зателефонувати до sloppy<char**>()ініціалізатора безпосередньо там.
Michał Górny

Звичайно, це все та ж операція, що і в стилі C, але з використанням альтернативного синтаксису C ++. Думаю, це може перешкодити читачам використовувати касти у стилі С в інших ситуаціях. Наприклад, синтаксис C ++ не буде працювати для акторського складу, (char**)&inякщо ви спочатку не зробите typedef для char**.
Steve Jessop

Хороший хак. Для повноти, ви могли б зробити це (a) завжди беручи const char * const *, припускаючи, що змінна не повинна змінюватися, або (b) параметризуючи будь-які два типи і роблячи її const між ними.
Джек В.

33

Ви можете розрізнити між двома оголошеннями, перевіривши підпис заявленої функції. Ось базовий приклад шаблонів, необхідних для перевірки типу параметра. Це можна легко узагальнити (або ви можете скористатися функціональними рисами 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 */);
}

(Цю останню логіку можна було б очистити та узагальнити; я намагався зробити кожен її фрагмент явним, щоб, сподіваюся, було зрозуміліше, як це працює.)


3
Там приємна магія. :) Я б підтримав, бо, схоже, це відповідає на запитання, але я не перевірив, що це працює, і я не знаю достатньо жорсткого C ++, щоб знати, чи так, просто дивлячись на нього. :)
Almo,

7
Як примітка: decltypeпотрібен C ++ 11.
Michał Górny

1
+1 Лол ... так, щоб уникнути #ifdefперевірки платформи, у вас у кінцевому підсумку буде 30 непарних рядків коду :) Хоча хороший підхід (хоча я в останні кілька днів хвилювався, дивлячись на запитання щодо SO, щоб люди, які цього не роблять по-справжньому розумію, що вони роблять, почав використовувати SFINAE як золотий молоток ... не у вашому випадку, але я боюся, що код стане більш складним і важким для обслуговування ...)
Девід Родрігес - dribeas

11
@ DavidRodríguez-dribeas: :-) Я просто дотримуюсь золотого правила сучасного C ++: якщо щось не є шаблоном, запитайте себе, "чому це не шаблон?" то зробіть це шаблоном.
James McNellis

1
[Перш ніж хтось прийме цей останній коментар занадто серйозно: це жарт. Начебто ...]
Джеймс МакНелліс,

11

Ви можете використовувати наступне:

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)яких компілятор буде нескінченно повторюватися.


2
Приємно! Я думаю, що це рішення має потенціал: мені подобається використовувати дозвіл на перевантаження для вибору шаблону лише тоді, коли функція не відповідає точно. Для роботи, однак, його const_castпотрібно було б перенести в add_or_remove_constте, що копається в, T**щоб визначити, чи Tє, constі додати або видалити кваліфікацію відповідно. Це все одно було б (набагато) простіше, ніж рішення, яке я продемонстрував. Трохи попрацювавши, це рішення також може змусити працювати без const_cast(тобто, використовуючи локальну змінну у вашому iconv).
James McNellis

Я щось пропустив? У випадку, коли дійсне iconvне є const, не Tвиводиться як const char**, що означає, що параметр inbufмає тип const T, тобто const char **const, і виклик iconvу шаблоні просто викликає себе? Як каже Джеймс, однак, з відповідною модифікацією типу Tцей трюк є основою чогось, що працює.
Steve Jessop

Чудове, розумне рішення. +1!
Linuxios

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

Тут ви маєте ідентифікатори всіх операційних систем. Для мене немає сенсу намагатися робити щось, що залежить від операційної системи, не перевіряючи цю систему. Це все одно, що купувати зелені штани, але не дивлячись на них.


13
Але допитувач прямо говорить without resorting to trying to detect the platform...
Фредерік Хаміді

1
@Linuxios: до тих пір , поки постачальники Linux або Apple , вирішують , що вони дійсно хочуть , щоб слідувати стандарту POSIX . Таке кодування, як відомо, важко підтримувати.
Fred Foo

2
@larsmans: Linux і Mac OS X роблять слідувати стандарту . Ваше посилання з 1997 року. Позаду це FreeBSD.
dreamlax

3
@Linuxios: Ні, це не [краще]. Якщо ви дійсно хочете зробити перевірку платформи, використовуйте autoconf або подібний інструмент. Перевірте фактичний прототип, а не робіть припущення, які в якийсь момент не вдасться, і це не вдасться користувачеві.
Michał Górny

2
@ MichałGórny: Гарна думка. Чесно кажучи, я повинен просто вийти з цього питання. Здається, я не можу нічого до цього внести.
Linuxios

1

Ви вказали, що використання власної функції обгортки є прийнятним. Ви, схоже, готові жити з попередженнями.

Отже, замість того, щоб писати свою обгортку на 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... */
                );
}

1

Як щодо

static void Test(char **)
{
}

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

EDIT: звичайно, "без виявлення платформи" - це трохи проблема. На жаль :-(

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

Проблема в тому, що на іншій платформі він не буде компілювати (тобто якщо функція прийме a, const char**вона не вдасться)
Девід Родрігес - dribeas

1

А як на рахунок:

#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**є так звані "подібні типи". Ви не збираєтеся уникнути цього порушення суворого псевдоніму, окрім як створення a 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 конст-коректності, оскільки все iconvце робиться зinbuf - це збільшення покажчика, що зберігається в ньому. Отже, ми «відкидаємо const» від покажчика, отриманого з вказівника, який не був const, коли ми його вперше побачили.

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


Код "суперпедантизму" непотрібний. На GCC4.7 з поточним stdlibc ++ це потрібно для компіляції.
Конрад Рудольф,

1

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

Ви шукаєте те, iconv.m4що встановлюється пакетом gettext.

AFAICS це просто:

AM_ICONV

в configure.ac, і він повинен виявити правильний прототип.

Потім у коді, який ви використовуєте:

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

використовувати для цього спеціалізацію шаблонів. Дивись вище.
Алекс

1
Дякую! Я вже використовую автоінструменти, і це, здається, є стандартним способом вирішити проблему, тому вона повинна бути ідеальною! На жаль, мені не вдалося отримати autoconf, щоб знайти файл iconv.m4 (і, здається, він не існує в OS X, яка має давню версію автоінструментів), тому мені не вдалося змусити його працювати портативно . Гугл показує, що багато людей мають проблеми з цим макросом. О, автоінструменти!
ridiculous_fish

Думаю, у моїй відповіді є потворний, але не ризикований хак. І все-таки, якщо ви вже використовуєте autoconf, і якщо необхідна конфігурація існує на платформах, які вам важливі, немає жодної реальної причини не використовувати це ...
Стів Джессоп,

У моїй системі цей файл .m4 встановлюється gettextпакетом. Крім того, досить часто пакунки включають використані макроси в m4/каталог і мають ACLOCAL_AMFLAGS = -I m4в Makefile.am. Я думаю, autopoint навіть копіює його до цього каталогу за замовчуванням.
Michał Górny

0

Я спізнюся на цю вечірку, але все ж, ось моє рішення:

// 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);
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.