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


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

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

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

これは、iconvCリンケージが必要なために失敗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
このような関数のキャストは移植性がありません。
ジョナサングリンスパン、2012

2
@dreamlax:バグレポートを送信しても効果はありません。FreeBSDの現在のバージョンは明らかにすでに持っていiconvなくてconstsvnweb.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*を取り、それをiconvの2番目のパラメーターが要求char**するものconst char*にかかわらず、a またはa に変換します。

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


これは非常にうまく機能し、C ++ 11を必要とせずに安全で簡単なようです。一緒に行くよ!ありがとう!
ridiculous_fish

2
私の回答で述べたように、これはC ++ 03の厳密なエイリアスに違反していると思うので、その意味で C ++ 11 必要です。誰かがこれを守りたいのなら、私は間違っているかもしれません。
スティーブジェソップ2012

1
C ++でCスタイルのキャストを推奨しないでください。私が間違っていない限り、sloppy<char**>()イニシャライザを直接呼び出すことができます。
ミハウゴルニー

もちろん、Cスタイルのキャストと同じ操作ですが、代替のC ++構文を使用しています。読者が他の状況でCスタイルのキャストを使用するのを思いとどまらせるかもしれません。たとえば、(char**)&in最初にのtypedefを作成しない限り、C ++構文はキャストに対して機能しませんchar**
スティーブジェソップ

良いハック。完全を期すために、(a)常にconst char * const *を取り、変数が変更されないことを想定している、または(b)任意の2つの型でパラメーター化してそれらの間でconstキャストすることで、おそらくこれを行うことができます。
ジャックV.

33

宣言した関数のシグネチャを調べることにより、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;
}

あなたは、パラメータの種類の資格を検出することができたら、コールに、2つのラッパー関数を書くことができるiconv1呼び出している:iconvchar const**引数と1通話ことiconvchar**引数。

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

次に、これらの使用法をaでラップして、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 ++を知りません。:)
アルモ

7
注:decltypeC ++ 11が必要です。
のMichałGórny

1
+1 Lol ... #ifdefプラットフォームのチェックを回避するために、コードの奇数行が30行になります:)素晴らしいアプローチです(私は過去数日間、SOに関する質問を見て心配していないので本当に彼らは黄金のハンマー...ないあなたのケースとしてSFINAEを使用して開始している何をしているかを理解し、私はコードが...)維持するために、より複雑かつ困難になるだろう恐れ
dribeas -デビッド・ロドリゲス

11
@DavidRodríguez-dribeas::-)私は最新のC ++の黄金律に従います:何かがテンプレートでない場合は、「なぜこれがテンプレートではないのですか?」それをテンプレートにします。
James McNellis、2012

1
[最後のコメントをだれもが真剣に受け止める前に:それは冗談です。並べ替え...]
James McNellis、2012

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**Tconstconst_casticonv
James McNellis、2012

私は何かを見逃しましたか?実数iconvが非constである場合、はTとして推定されません。const char**つまり、パラメーターのinbufconst Tconst char **constでありiconv、テンプレート内のへの呼び出しはそれ自体を呼び出すだけですか?ジェームズが言うように、型に適切な変更を加えると、Tこのトリックは機能するものの基礎になります。
スティーブジェソップ2012

素晴らしい、賢いソリューション。+1!
Linuxios

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

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


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 2012

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

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

編集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**、それは失敗します)
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**、いわゆる「類似タイプ」です。を作成しconst char*、それをに設定し、temporaryへのポインターを使用して*foo呼び出しiconv、結果を*fooaの後ろにコピーする以外に、厳密なエイリアスの違反を回避するつもりはありません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正しさのPOVから安全であるiconvとしinbuf、それに格納されているポインタをインクリメントされます。つまり、最初にそれを目にしたときに非constだったポインターから派生したポインターから「constをキャスト」しています。

また、過負荷の書くことができるmyconvmyconv_helperテイクそのconst char **inbuf呼び出し側が渡しするかどうかの選択肢があるように、他の方向に約と台無し事をconst char**char**iconvC ++ではそもそもどちらが呼び出し元に与えられるべきかは間違いありませんが、もちろんインターフェースはCからコピーされるだけで、関数のオーバーロードはありません。


「スーパーペダントリー」コードは不要です。現在のstdlibc ++を使用するGCC4.7では、これをコンパイルする必要があります。
Konrad Rudolph

1

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

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

AFAICSはそれだけです:

AM_ICONV

configure.acでは、正しいプロトタイプを検出する必要があります。

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

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

そのためにテンプレートの特殊化を使用します。上記を参照。
Alex

1
ありがとう!私はすでにautotoolsを使用していますが、これは問題を回避するための標準的な方法のようですので、完璧なはずです!残念ながら、autoconfでiconv.m4ファイルを見つけることができませんでした(そして、それは古いバージョンのautotoolsがインストールされているOS Xには存在しないようです)。 。周りをググると、多くの人がこのマクロで問題を抱えていることを示しています。ああ、autotools!
ridiculous_fish

私は私の答えに醜いが危険ではないハックがあると思います。それでも、autoconfをすでに使用していて、気になるプラットフォームに必要な構成が存在する場合、これを使用しない本当の理由はありません...
Steve Jessop

私のシステムでは、.m4ファイルはgettextパッケージによってインストールされます。また、パッケージはで使用されるマクロを含めることは、ごく一般的なことですm4/ディレクトリと持っているACLOCAL_AMFLAGS = -I m4Makefile.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);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.