91

在我的 Linux(和 OS X)机器上,该iconv()函数具有以下原型:

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

在 FreeBSD 上它看起来像这样:

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

我希望我的 C++ 代码能够在两个平台上构建。char**对于 C 编译器,为参数传递 a const char**(反之亦然)通常只会发出警告;但是在 C++ 中,这是一个致命错误。所以如果我通过 a char**,它不会在 BSD 上编译,如果我通过 aconst 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);

但这有可能掩盖其他更严重的错误。

4

9 回答 9

56

如果你想要的只是对一些 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() 接受 achar**或 aconst char*并将其转换为 achar**或 a const char*,无论 iconv 的第二个参数需要什么。

更新:更改为使用 const_cast 并调用 sloppy 而不是 as cast。

于 2012-07-10T21:45:51.920 回答
33

您可以通过检查声明函数的签名来消除两个声明之间的歧义。这是检查参数类型所需的模板的基本示例。这可以很容易地概括(或者您可以使用 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:一个iconvchar const**参数调用iconv,一个用char**参数调用。

因为应该避免函数模板特化,所以我们使用类模板来进行特化。请注意,我们还将每个调用程序都设为函数模板,以确保仅实例化我们使用的特化。如果编译器试图为错误的特化生成代码,你会得到错误。

然后我们用 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 */);
}

(后一种逻辑可以被清理和概括;我试图让每一部分都明确,希望能更清楚地说明它是如何工作的。)

于 2012-07-10T20:52:59.090 回答
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),这将使编译器无限重复。

于 2012-07-10T21:05:30.177 回答
7
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

在这里,您有所有操作系统的 ID。对我来说,在不检查这个系统的情况下尝试做一些依赖于操作系统的事情没有任何意义。这就像买了一条绿裤子,却不看它们。

于 2012-07-10T20:36:28.230 回答
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;
}
于 2012-07-10T20:41:12.530 回答
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... */
                );
}
于 2012-07-10T20:50:50.080 回答
1

更新:现在我看到可以在没有自动工具的情况下在 C++ 中处理它,但我将 autoconf 解决方案留给寻找它的人。

您正在寻找的是iconv.m4由 gettext 包安装的。

AFAICS 只是:

AM_ICONV

在 configure.ac 中,它应该检测到正确的原型。

然后,在您使用的代码中:

#ifdef ICONV_CONST
// const char**
#else
// char**
#endif
于 2012-07-10T20:53:22.367 回答
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++11const char**char**是所谓的“相似类型”。const char*除了通过创建 a ,将其设置为等于*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-correctness 的 POV 来说是安全的,因为iconv所做的inbuf只是增加存储在其中的指针。因此,当我们第一次看到它时,我们从一个派生自非 const 指针的指针中“抛弃 const”。

我们还可以编写一个重载的myconvand myconv_helper,它在另一个方向上把const char **inbuf事情搞砸了,这样调用者就可以选择是传入 aconst char**还是传入 a char**。可以说,它iconv应该首先在 C++ 中提供给调用者,但是当然接口只是从没有函数重载的 C 中复制而来。

于 2012-07-10T22:18:16.570 回答
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);
}
于 2012-07-21T21:35:44.923 回答