91 votes

Comment puis-je appeler de manière portative une fonction C++ qui prend un char** sur certaines plateformes et un const char** sur d'autres ?

Sur mes machines Linux (et OS X), la fonction iconv() a ce prototype :

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

alors que sous FreeBSD, cela ressemble à ceci :

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

J'aimerais que mon code C++ soit compatible avec les deux plates-formes. Avec les compilateurs C, passer un char** pour un const char** (ou vice versa) émet généralement un simple avertissement ; cependant, en C++, c'est une erreur fatale. Ainsi, si je passe un char** il ne compilera pas sous BSD, et si je passe une option const char** il ne compile pas sur Linux / OS X. Comment puis-je écrire un code qui compile sur les deux, sans avoir à essayer de détecter la plate-forme ?

Une idée (ratée) que j'ai eue était de fournir un prototype local qui remplace celui fourni par l'en-tête :

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

Cela échoue parce que iconv a besoin d'une liaison C, et vous ne pouvez pas mettre extern "C" à l'intérieur d'une fonction (pourquoi pas ?)

La meilleure idée que j'ai trouvée est de couler le pointeur de fonction lui-même :

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

mais cela peut masquer d'autres erreurs plus graves.

57voto

Nordic Mainframe Points 13717

Si ce que vous voulez, c'est simplement fermer les yeux sur certains problèmes liés aux constantes, alors vous pouvez utiliser une conversion qui estompe la distinction, c'est-à-dire qui rend les char** et les constantes char** interopérables :

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

Puis plus tard dans le programme :

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

sloppy() prend un char** ou un const char* et le convertit en un char** ou un const char* quel que soit le second paramètre d'iconv.

MISE À JOUR : modifié pour utiliser const_cast et appeler sloppy not a as cast.

33voto

James McNellis Points 193607

Vous pouvez faire la distinction entre les deux déclarations en inspectant la signature de la fonction déclarée. Voici un exemple de base des modèles nécessaires pour inspecter le type de paramètre. Cela pourrait facilement être généralisé (ou vous pourriez utiliser les traits de fonction de Boost), mais cela est suffisant pour démontrer une solution à votre problème spécifique :

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

Voici un exemple qui démontre ce comportement :

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

Une fois que vous pouvez détecter la qualification du type de paramètre, vous pouvez écrire deux fonctions enveloppes qui appellent iconv : celui qui appelle iconv avec un char const** et un autre qui appelle iconv avec un char** argument.

Comme la spécialisation des modèles de fonction doit être évitée, nous utilisons un modèle de classe pour effectuer la spécialisation. Notez que nous faisons également de chacun des invokers un template de fonction, pour nous assurer que seule la spécialisation que nous utilisons est instanciée. Si le compilateur essaie de générer du code pour la mauvaise spécialisation, vous obtiendrez des erreurs.

Nous enveloppons ensuite l'utilisation de ces éléments avec un call_iconv pour rendre cet appel aussi simple que d'appeler iconv directement. Ce qui suit est un modèle général montrant comment cela peut être écrit :

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

(Cette dernière logique pourrait être nettoyée et généralisée ; j'ai essayé d'en rendre chaque élément explicite pour espérer que son fonctionnement soit plus clair).

11voto

Krizz Points 7273

Vous pouvez utiliser les éléments suivants :

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

Vous pouvez passer const char** et sur Linux/OSX, il passera par la fonction template et sur FreeBSD, il ira directement à iconv .

Inconvénient : cela permettra des appels comme iconv(foo, 2.5) ce qui mettra le compilateur en récurrence infinie.

7voto

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

Ici vous avez les identifiants de tous les systèmes d'exploitation. Pour moi, cela n'a aucun intérêt d'essayer de faire quelque chose qui dépend du système d'exploitation sans vérifier ce système. C'est comme acheter un pantalon vert mais sans le regarder.

1voto

Christian Stieber Points 7021

Et si

static void Test(char **)
{
}

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

EDIT : bien sûr, le "sans détecter la plateforme" est un peu un problème. Oups :-(

EDIT 2 : ok, version améliorée, peut-être ?

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X