¿Cómo puedo llamar de forma portátil a una función de C ++ que toma un carácter ** en algunas plataformas y un carácter constante ** en otras?

91

En mis máquinas Linux (y OS X), la iconv()función tiene este prototipo:

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

mientras que en FreeBSD se ve así:

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

Me gustaría que mi código C ++ se construyera en ambas plataformas. Con los compiladores de C, pasar a char**para un const char**parámetro (o viceversa) normalmente emite una simple advertencia; sin embargo, en C ++ es un error fatal. Entonces, si paso a char**, no se compilará en BSD, y si paso a const char**, no se compilará en Linux / OS X. ¿Cómo puedo escribir código que se compile en ambos, sin tener que intentar detectar la plataforma?

Una idea (fallida) que tuve fue proporcionar un prototipo local que anule cualquiera proporcionado por el encabezado:

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

Esto falla porque iconvnecesita un enlace C y no puede poner extern "C"dentro de una función (¿por qué no?)

La mejor idea de trabajo que se me ocurrió es lanzar el puntero de función en sí:

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

pero esto tiene el potencial de enmascarar otros errores más graves.

ridiculous_fish
fuente
31
Infierno de una pregunta para su primera en SO. :)
Almo
24
Registra un error en FreeBSD. La implementación POSIX de iconvrequiere inbufque no sea constante.
dreamlax
3
Transmitir la función así no es portátil.
Jonathan Grynspan
2
@dreamlax: es poco probable que enviar un informe de error tenga algún efecto; la versión actual de FreeBSD aparentemente ya tiene iconvsin const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo
2
@larsmans: ¡Es bueno saberlo! Nunca he usado FreeBSD, pero es bueno saber que la última versión es compatible con el último estándar.
dreamlax

Respuestas:

57

Si lo que desea es hacer la vista gorda ante algunos problemas de constantes, puede utilizar una conversión que difumine la distinción, es decir, hace que char ** y const char ** sean interoperables:

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

Luego, más adelante en el programa:

iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);

sloppy () toma a char**o a const char*y lo convierte en a char**o a const char*, lo que sea que exija el segundo parámetro de iconv.

ACTUALIZACIÓN: cambiado para usar const_cast y llamar a descuidado no como cast.

Mainframe nórdico
fuente
Esto funciona bastante bien y parece ser seguro y sencillo sin requerir C ++ 11. ¡Voy con eso! ¡Gracias!
ridiculous_fish
2
Como dije en mi respuesta, creo que esto viola el aliasing estricta en C ++ 03, por lo que en ese sentido no requiere C ++ 11. Sin embargo, podría estar equivocado si alguien quiere defender esto.
Steve Jessop
1
No fomente las conversiones de estilo C en C ++; a menos que me equivoque, puede llamar al sloppy<char**>()inicializador directamente allí.
Michał Górny
Sigue siendo la misma operación que un elenco de estilo C, por supuesto, pero usando la sintaxis alternativa de C ++. Supongo que podría disuadir a los lectores de usar moldes de estilo C en otras situaciones. Por ejemplo, la sintaxis de C ++ no funcionaría para el reparto a (char**)&inmenos que primero hagas un typedef para char**.
Steve Jessop
Buen truco. Para completar, probablemente podría hacer esto (a) siempre tomando un const char * const *, asumiendo que la variable no debe ser alterada o (b) parametrizando por dos tipos cualesquiera y haciendo que sea constante entre ellos.
Jack V.
33

Puede eliminar la ambigüedad entre las dos declaraciones inspeccionando la firma de la función declarada. A continuación, se muestra un ejemplo básico de las plantillas necesarias para inspeccionar el tipo de parámetro. Esto podría generalizarse fácilmente (o podría usar los rasgos de función de Boost), pero esto es suficiente para demostrar una solución para su problema específico:

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

Aquí hay un ejemplo que demuestra el comportamiento:

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

Una vez que pueda detectar la calificación del tipo de parámetro, puede escribir dos funciones contenedoras que llaman iconv: una que llama iconvcon un char const**argumento y otra que llama iconvcon un char**argumento.

Debido a que debe evitarse la especialización de la plantilla de función, usamos una plantilla de clase para realizar la especialización. Tenga en cuenta que también hacemos de cada uno de los invocadores una plantilla de función, para asegurarnos de que solo se instancia la especialización que usamos. Si el compilador intenta generar código para la especialización incorrecta, obtendrá errores.

Luego ajustamos el uso de estos con un call_iconvpara que llamar a esto sea tan simple como llamar iconvdirectamente. El siguiente es un patrón general que muestra cómo se puede escribir esto:

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

(Esta última lógica podría limpiarse y generalizarse; he intentado hacer que cada parte de ella sea explícita para, con suerte, dejar más claro cómo funciona).

James McNellis
fuente
3
Bonita magia allí. :) Yo votaría porque parece que responde a la pregunta, pero no he verificado que funcione, y no sé lo suficiente sobre C ++ como para saber si lo hace con solo mirarlo. :)
Almo
7
Como nota: decltyperequiere C ++ 11.
Michał Górny
1
+1 Lol ... así que para evitar una #ifdefverificación de la plataforma, terminas con 30 líneas impares de código :) Sin embargo, un buen enfoque (aunque me he estado preocupando en los últimos días mirando preguntas sobre SO que las personas que no realmente entiendo lo que están haciendo han comenzado a usar SFINAE como un martillo de oro ... no es su caso, pero me temo que el código se volverá más complejo y difícil de mantener ...)
David Rodríguez - dribeas
11
@ DavidRodríguez-dribeas: :-) Simplemente sigo la regla de oro del C ++ moderno: si algo no es una plantilla, pregúntate, "¿por qué no es esto una plantilla?" luego conviértalo en una plantilla.
James McNellis
1
[Antes de que alguien se tome demasiado en serio ese último comentario: es una broma. Más o menos ...]
James McNellis
11

Puede utilizar lo siguiente:

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

Puede pasar const char**y en Linux / OSX pasará por la función de plantilla y en FreeBSD irá directamente a iconv.

Inconveniente: permitirá llamadas como las iconv(foo, 2.5)que pondrán al compilador en repetición infinita.

Krizz
fuente
2
¡Agradable! Creo que esta solución tiene potencial: me gusta el uso de la resolución de sobrecarga para seleccionar la plantilla solo cuando la función no coincide exactamente. Sin embargo, para que funcione, const_castsería necesario moverlo a un add_or_remove_constdispositivo que profundice en el T**para detectar si lo Testá consty agregar o eliminar la calificación según corresponda. Esto aún sería (mucho) más sencillo que la solución que he demostrado. Con un poco de trabajo, también podría ser posible hacer que esta solución funcione sin el const_cast(es decir, usando una variable local en su iconv).
James McNellis
¿Me he perdido algo? En el caso de que lo real iconvno sea constante, no se Tdeduce como const char**, lo que significa que el parámetro inbuftiene tipo const T, que es const char **const, y la llamada a iconven la plantilla simplemente se llama a sí misma. Sin embargo, como dice James, con una modificación adecuada del tipo, Teste truco es la base de algo que funciona.
Steve Jessop
Solución asombrosa e inteligente. +1!
Linuxios
7
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

Aquí tienes los identificadores de todos los sistemas operativos. Para mí no tiene ningún sentido intentar hacer algo que depende del sistema operativo sin verificar este sistema. Es como comprar unos pantalones verdes pero sin mirarlos.

Sangre
fuente
13
Pero el interrogador dice explícitamente without resorting to trying to detect the platform...
Frédéric Hamidi
1
@Linuxios: hasta Linux o Apple proveedores deciden que no quieren seguir el estándar POSIX . Este tipo de codificación es muy difícil de mantener.
Fred Foo
2
@larsmans: Linux y Mac OS X hacer seguir la norma . Su enlace es de 1997. Es FreeBSD el que está detrás.
Dreamlax
3
@Linuxios: No, no es [mejor]. Si realmente desea realizar comprobaciones de la plataforma, utilice autoconf o una herramienta similar. Verifique el prototipo real en lugar de hacer suposiciones que fallarán en algún momento, y fallarán en el usuario.
Michał Górny
2
@ MichałGórny: Buen punto. Francamente, debería salir de esta cuestión. No parece que pueda contribuir en nada.
Linuxios
1

Ha indicado que utilizar su propia función de contenedor es aceptable. También parece estar dispuesto a vivir con las advertencias.

Entonces, en lugar de escribir su contenedor en C ++, escríbalo en C, donde solo recibirá una advertencia en algunos sistemas:

// 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
fuente
1

Qué tal si

static void Test(char **)
{
}

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

EDITAR: por supuesto, el "sin detectar la plataforma" es un problema. Vaya :-(

EDIT 2: ok, versión mejorada, ¿quizás?

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
fuente
El problema con eso es que en la otra plataforma no se compilará (es decir, si la función toma un const char**fallará)
David Rodríguez - dribeas
1

Qué pasa:

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

Creo que esto viola el alias estricto en C ++ 03, pero no en C ++ 11 porque en C ++ 11 const char**y char**son los llamados "tipos similares". No va a evitar esa violación del aliasing estricto más que creando un const char*, configúrelo igual a *foo, llame iconvcon un puntero al temporal y luego vuelva a copiar el resultado *foodespués de un 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;
}

Esto está a salvo del punto de vista de la corrección constante, porque todo lo que iconvhace inbufes incrementar el puntero almacenado en él. Así que estamos "desechando const" de un puntero derivado de un puntero que no era constante cuando lo vimos por primera vez.

También podríamos escribir una sobrecarga de myconvy myconv_helperque tome const char **inbufy altere las cosas en la otra dirección, de modo que la persona que llama tenga la opción de pasar a const char**o a char**. Lo que podría decirse que iconvdebería haberle dado a la persona que llama en primer lugar en C ++, pero, por supuesto, la interfaz simplemente se copia de C donde no hay sobrecarga de funciones.

Steve Jessop
fuente
El código de "superpedantería" es innecesario. En GCC4.7 con un stdlibc ++ actual, lo necesita para compilar.
Konrad Rudolph
1

Actualización: ahora veo que es posible manejarlo en C ++ sin autotools, pero dejo la solución de autoconf para las personas que la buscan.

Lo que está buscando es iconv.m4cuál está instalado por el paquete gettext.

AFAICS es solo:

AM_ICONV

en configure.ac, y debería detectar el prototipo correcto.

Luego, en el código que usa:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
Michał Górny
fuente
use la especialización de plantillas para eso. véase más arriba.
Alex
1
¡Gracias! Ya uso autotools, y esta parece ser la forma estándar de solucionar el problema, ¡así que debería ser perfecto! Desafortunadamente, no pude obtener autoconf para encontrar el archivo iconv.m4 (y parece que no existe en OS X, que tiene una versión antigua de autotools), por lo que no pude hacer que funcione de manera portátil . Buscar en Google muestra que muchas personas tienen problemas con esta macro. ¡Oh, autotools!
ridiculous_fish
Creo que tengo un truco feo pero no arriesgado en mi respuesta. Aún así, si ya está usando autoconf, y si la configuración necesaria existe en las plataformas que le interesan, no hay una razón real para no usar esto ...
Steve Jessop
En mi sistema, ese archivo .m4 se instala por gettextpaquete. Además, es bastante común que los paquetes incluyan macros usadas en el m4/directorio y tengan ACLOCAL_AMFLAGS = -I m4archivos Makefile.am. Creo que autopoint incluso lo copia en ese directorio de forma predeterminada.
Michał Górny
0

Llego tarde a esta fiesta pero aún así, aquí está mi solución:

// 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
fuente