私の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);
しかし、これは他のより深刻なエラーを隠す可能性があります。
必要なのが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を呼び出すように変更されました。
宣言した関数のシグネチャを調べることにより、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つはiconv
をchar const**
引数で呼び出し、もう1つは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 */);
}
(この後者のロジックは、クリーンアップして一般化することができます。うまくいけば、それがどのように機能するかを明確にするために、その各部分を明示的にしようとしました。)
次のものを使用できます。
_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)
のような呼び出しが可能になり、コンパイラーが無限に繰り返し実行されます。
#ifdef __linux__
... // linux code goes here.
#Elif __FreeBSD__
... // FreeBSD code goes here.
#endif
ここ すべてのオペレーティングシステムのIDがあります。私にとっては、このシステムをチェックせずにオペレーティングシステムに依存するものを試してみる意味はありません。緑のズボンを買うようなものですが、それらを見ないでください。
独自のラッパー関数の使用が許容されることを示しました。あなたはまた、警告を伴って生きる気があるようです。
したがって、ラッパーを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... */
);
}
更新:autotoolsなしでC++で処理できることがわかりましたが、autoconfソリューションを探している人のために残しておきます。
あなたが探しているのはiconv.m4
はgettextパッケージによってインストールされます。
AFAICSはそれだけです:
AM_ICONV
configure.acで、それは正しいプロトタイプを検出するはずです。
次に、使用するコードで:
#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
何について:
#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から安全です。これは、iconv
がinbuf
で行うすべてのことは、格納されているポインタをインクリメントするためです。つまり、最初にそれを目にしたときに非constだったポインターから派生したポインターから「constをキャスト」しています。
また、myconv
とmyconv_helper
のオーバーロードを記述して、const char **inbuf
を取得し、逆方向に混乱させることで、呼び出し元がconst char**
またはchar**
。 C++の最初の場所でiconv
が呼び出し元に与えられたはずですが、当然、インターフェイスはCからコピーされるだけで、関数のオーバーロードはありません。
いかがですか
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;
}
私はこのパーティーに遅れましたが、それでも、これが私の解決策です:
// 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);
}