web-dev-qa-db-ja.com

一部のプラットフォームではchar **を、他のプラットフォームではconst char **をとるC ++関数を移植して呼び出すにはどうすればよいですか?

私の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++では致命的なエラーです。したがって、char**を渡した場合、BSDでコンパイルできません。const char**を渡した場合、Linux/OS Xではコンパイルできません。両方でコンパイルするコードを作成するにはどうすればよいですか。プラットフォームを検出しようとせずに?

私が持っていた(失敗した)1つのアイデアは、ヘッダーによって提供されたものをオーバーライドするローカルプロトタイプを提供することでした。

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

しかし、これは他のより深刻なエラーを隠す可能性があります。

91
ridiculous_fish

必要なのが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()は、char**またはconst char*を受け取り、それをchar**またはconst char*に変換します。これは、iconvの2番目のパラメーターで要求されます。

更新:const_castを使用し、asキャストではなくsloppyを呼び出すように変更されました。

57

宣言した関数のシグネチャを調べることにより、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を呼び出す2つのラッパー関数を記述できます。1つはiconvchar const**引数で呼び出し、もう1つはiconvchar**引数。

関数テンプレートの特殊化は避ける必要があるため、クラステンプレートを使用して特殊化を行います。また、使用する特殊化のみが確実にインスタンス化されるように、各呼び出し元を関数テンプレートにします。コンパイラが誤った特殊化用のコードを生成しようとすると、エラーが発生します。

次に、これらの使用法を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 */);
}

(この後者のロジックは、クリーンアップして一般化することができます。うまくいけば、それがどのように機能するかを明確にするために、その各部分を明示的にしようとしました。)

33
James McNellis

次のものを使用できます。

_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)のような呼び出しが可能になり、コンパイラーが無限に繰り返し実行されます。

11
Krizz
#ifdef __linux__
... // linux code goes here.
#Elif __FreeBSD__
... // FreeBSD code goes here.
#endif

ここ すべてのオペレーティングシステムのIDがあります。私にとっては、このシステムをチェックせずにオペレーティングシステムに依存するものを試してみる意味はありません。緑のズボンを買うようなものですが、それらを見ないでください。

7
Blood

独自のラッパー関数の使用が許容されることを示しました。あなたはまた、警告を伴って生きる気があるようです。

したがって、ラッパーを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
Michael Burr

更新:autotoolsなしでC++で処理できることがわかりましたが、autoconfソリューションを探している人のために残しておきます。

あなたが探しているのはiconv.m4はgettextパッケージによってインストールされます。

AFAICSはそれだけです:

AM_ICONV

configure.acで、それは正しいプロトタイプを検出するはずです。

次に、使用するコードで:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
1
Michał Górny

何について:

#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++ 11ではconst char**char**はいわゆる「類似の型」であるため、これはC++ 03では厳密なエイリアスに違反していると思います。 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;
}

これは、const-correctnessのPOVから安全です。これは、iconvinbufで行うすべてのことは、格納されているポインタをインクリメントするためです。つまり、最初にそれを目にしたときに非constだったポインターから派生したポインターから「constをキャスト」しています。

また、myconvmyconv_helperのオーバーロードを記述して、const char **inbufを取得し、逆方向に混乱させることで、呼び出し元がconst char**またはchar**。 C++の最初の場所でiconvが呼び出し元に与えられたはずですが、当然、インターフェイスはCからコピーされるだけで、関数のオーバーロードはありません。

1
Steve Jessop

いかがですか

static void Test(char **)
{
}

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

編集:もちろん、「プラットフォームを検出せずに」は少し問題です。 Oops :-(

編集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;
}
1

私はこのパーティーに遅れましたが、それでも、これが私の解決策です:

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