在我的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上编译。如何编写可在两者上编译的代码,没有试图检测平台?
我遇到的一个(失败的)想法是提供一个覆盖标题提供的本地原型:
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);
但这有可能掩盖其他更严重的错误。
答案 0 :(得分: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()会使用char**
或const char*
并将其转换为char**
或const char*
,无论iconv要求的第二个参数如何。
UPDATE:更改为使用const_cast并调用sloppy而不是as cast。
答案 1 :(得分:34)
您可以通过检查声明的函数的签名来消除两个声明之间的歧义。以下是检查参数类型所需模板的基本示例。这可以很容易地推广(或者你可以使用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
的包装函数:一个调用带有iconv
参数的char const**
和一个调用iconv
的函数。 1}}带有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 */);
}
(后一种逻辑可以被清理和推广;我已经尝试将每一部分都明确化,以便更清楚地说明它是如何工作的。)
答案 2 :(得分: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)
之类的调用,这会使编译器无限复发。
答案 3 :(得分:7)
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif
Here您拥有所有操作系统的ID。对我来说,没有任何意义,在没有检查这个系统的情况下尝试做依赖于操作系统的事情。这就像买绿色长裤但不看它们。
答案 4 :(得分: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;
}
答案 5 :(得分: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... */
);
}
答案 6 :(得分:1)
更新:现在我看到可以在没有autotools的情况下在C ++中处理它,但我正在为寻找它的人留下autoconf解决方案。
您正在寻找的是iconv.m4
,它是由gettext包安装的。
AFAICS它只是:
AM_ICONV
在configure.ac中,它应该检测正确的原型。
然后,在您使用的代码中:
#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
答案 7 :(得分: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*
,将其设置为*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
并在另一个方向上混淆,这样调用者可以选择是否传递{ {1}}或const char**
。可以说char**
应该首先在C ++中给调用者,但当然接口只是从C中复制而没有函数重载。
答案 8 :(得分: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);
}