我如何可移植地调用C++函数,该函数在某些平台上采用char**而在其他平台上采用const char**?

rid*_*ish 91 c++ portability const

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

size_t iconv (iconv_t, char **inbuf...
Run Code Online (Sandbox Code Playgroud)

而在FreeBSD上它看起来像这样:

size_t iconv (iconv_t, const char **inbuf...
Run Code Online (Sandbox Code Playgroud)

我希望我的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);
}
Run Code Online (Sandbox Code Playgroud)

这个失败是因为iconv需要C链接,你不能放在extern "C"一个函数内(为什么不呢?)

我想出的最好的工作思路是抛出函数指针本身:

typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);
Run Code Online (Sandbox Code Playgroud)

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

Nor*_*ame 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); }
};
Run Code Online (Sandbox Code Playgroud)

然后在程序中:

iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);
Run Code Online (Sandbox Code Playgroud)

sloppy()接受a char**或a const char*并将其转换为a char**或a const char*,无论iconv要求的第二个参数如何.

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

  • 正如我在回答中所说,我认为这违反了C++ 03中的严格别名,所以从这个意义上来说它*需要C++ 11.我可能错了,如果有人想要捍卫这个. (2认同)

Jam*_*lis 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 };
};
Run Code Online (Sandbox Code Playgroud)

这是一个演示行为的示例:

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;
}
Run Code Online (Sandbox Code Playgroud)

一旦可以检测到参数类型的限定,就可以编写两个调用的包装函数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 */);
}
Run Code Online (Sandbox Code Playgroud)

(后一种逻辑可以被清理和推广;我已经尝试将每一部分都明确化,以便更清楚地了解它是如何工作的.)

  • @DavidRodríguez-dribeas::-)我只是遵循现代C++的黄金法则:如果某些东西不是模板,请问自己,"为什么这不是一个模板?" 然后把它变成一个模板. (11认同)
  • 注意:`decltype`需要C++ 11. (7认同)
  • 卓越!C++取消了,C++又回来了. (4认同)
  • 好神奇的.:)我赞成,因为它似乎回答了这个问题,但我还没有证实它有效,而且我不知道足够的核心C++知道它是否只是通过查看它.:) (3认同)

Kri*_*izz 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);
}
Run Code Online (Sandbox Code Playgroud)

您可以通过const char**Linux/OSX它将通过模板功能,在FreeBSD上它将直接进入iconv.

缺点:它将允许调用iconv(foo, 2.5)将使编译器无限重复.

  • 太好了!我认为这个解决方案有潜力:我喜欢使用重载分辨率来仅在函数不完全匹配时选择模板.但是,为了工作,需要将`const_cast`移动到`add_or_remove_const`中,该`add_or_remove_const`挖掘`T**'以检测`T`是否为`const`并根据需要添加或删除限定.这仍然比我演示的解决方案(远)更直接.通过一些工作,也可以使这个解决方案在没有`const_cast`的情况下工作(即,通过在`iconv`中使用局部变量). (2认同)

Blo*_*ood 7

#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif
Run Code Online (Sandbox Code Playgroud)

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

  • 但提问者明确表示"不试图试图检测平台"...... (13认同)
  • @Linuxios:不,这不是[更好].如果您确实想要进行平台检查,请使用autoconf或类似工具.检查实际的原型而不是做某些在某些时候会失败的假设,并且它将在用户上失败. (3认同)
  • @larsmans:Linux和Mac OS X [*do*遵循标准](http://pubs.opengroup.org/onlinepubs/009695399/functions/iconv.html).你的链接是从1997年开始的.FreeBSD落后了. (2认同)
  • @MichałGórny:好点.坦率地说,我应该摆脱这个问题.我似乎无法为此做出任何贡献. (2认同)