일부 플랫폼에서는 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에서 컴파일되지 않습니다. 플랫폼을 감지하지 않고 두 가지 모두에서 컴파일되는 코드를 어떻게 작성할 수 있습니까?

한 가지 (실패한) 아이디어는 헤더에서 제공하는 모든 것을 재정의하는 로컬 프로토 타입을 제공하는 것이 었습니다.

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(가) 필요 inbufconst가 아닌 것으로.
dreamlax

3
이와 같은 기능을 캐스팅하는 것은 이식 할 수 없습니다.
Jonathan Grynspan 2012

2
@dreamlax : 버그 보고서를 제출해도 효과가 없을 것 같습니다. FreeBSD의 현재 버전은 분명히 이미이 iconv를 빼고 const: svnweb.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의 두 번째 매개 변수가 요구하는대로 a char**또는 a 로 변환합니다 const char*.

업데이트 : const_cast를 사용하고 as cast가 아닌 sloppy를 호출하도록 변경되었습니다.


이것은 매우 잘 작동하며 C ++ 11 없이도 안전하고 간단 해 보입니다. 나는 그것으로 간다! 감사!
ridiculous_fish

2
내 대답에서 말했듯이 이것이 C ++ 03의 엄격한 앨리어싱을 위반한다고 생각하므로 그런 의미에서 C ++ 11 필요합니다. 누군가 이것을 옹호하고 싶다면 나는 틀릴 수도 있습니다.
Steve Jessop 2012

1
C ++에서 C 스타일 캐스트를 권장하지 마십시오. 내가 틀리지 않는 한 sloppy<char**>()초기화 프로그램을 직접 호출 할 수 있습니다.
Michał Górny

물론 C 스타일 캐스트와 동일한 작업이지만 대체 C ++ 구문을 사용합니다. 독자가 다른 상황에서 C 스타일 캐스트를 사용하지 못하게 할 수 있다고 생각합니다. 예를 들어 C ++ 구문은 (char**)&in먼저 typedef를 만들지 않는 한 캐스트에 대해 작동하지 않습니다 char**.
Steve Jessop 2012-07-11

좋은 해킹. 완전성을 위해 아마도 (a) 항상 const char * const *를 사용하여 변수가 변경되지 않아야한다고 가정하거나 (b) 두 유형에 의해 매개 변수화하여 둘 사이에 const 캐스트로 만들 수 있습니다.
Jack V.

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**인수로 호출하고 다른 하나 iconvchar**인수로 호출합니다 .

함수 템플릿 전문화는 피해야하므로 클래스 템플릿을 사용하여 전문화합니다. 또한 우리가 사용하는 전문화 만 인스턴스화되도록 각 호출자를 함수 템플릿으로 만듭니다. 컴파일러가 잘못된 전문화에 대한 코드를 생성하려고하면 오류가 발생합니다.

그런 다음 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 ++를 충분히 알지 못합니다. :)
Almo 2012

7
참고 : decltypeC ++ 11이 필요합니다.
Michał Górny

1
+1 Lol ... #ifdef플랫폼을 확인 하지 않으려면 30 줄의 이상한 코드로 끝납니다. 그래도 좋은 접근 방식입니다 (지난 며칠 동안 질문을 살펴보면서 걱정했습니다. 정말 그들은 황금 망치 ... 아닌 경우로 SFINAE를 사용하기 시작했다 무엇을하고 있는지 이해하지만, 나는 코드는 ...) 유지 관리가 더 복잡하고 어려워 질 것이다 두려워
dribeas - 데이비드 로드리게스

11
@ DavidRodríguez-dribeas : :-) 저는 현대 C ++의 황금률을 따를뿐입니다. 템플릿이 아닌 것이 있으면 스스로에게 물어보십시오. "이게 템플릿이 아닌 이유는 무엇입니까?" 그런 다음 템플릿으로 만드십시오.
James McNellis 2012

1
[누구나 마지막 댓글을 너무 심각하게 받아들이 기 전에 : 농담입니다. 정렬 ...의]
제임스 McNellis

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**여부를 감지 할 수 T있다 const적절하게 추가하거나 제거 할 자격. 이것은 내가 보여준 솔루션보다 여전히 (훨씬) 더 간단합니다. 약간의 작업을 통해이 솔루션이없이 const_cast(즉,에서 지역 변수를 사용하여 iconv) 작동하도록 할 수도 있습니다 .
James McNellis 2012

내가 뭔가를 놓친 적이 있습니까? 실제이 경우 iconv되지 const가 아닌이며, T같은 추론 const char**, 어떤 수단 매개 변수가 있다는 inbuf유형이 const T있다, const char **const그리고 호출 iconv템플릿은 단지 자신을 호출에 있습니까? James가 말했듯 T이 유형을 적절하게 수정하면 이 트릭이 작동하는 것의 기초가됩니다.
Steve Jessop

훌륭하고 영리한 솔루션. +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...
프레데릭 Hamidi

1
@Linuxios : 리눅스 업체 또는 Apple 그들이 결정할 때까지 않는 추적 할 POSIX 표준을 . 이런 종류의 코딩은 유지하기가 매우 어렵습니다.
Fred Foo 2012

2
@larsmans : Linux 및 Mac OS X 표준 . 귀하의 링크는 1997 년에 있습니다. 뒤에있는 것은 FreeBSD입니다.
dreamlax

3
@Linuxios : 아니요, [더 나아지지 않습니다]. 정말로 플랫폼 검사를하고 싶다면 autoconf 또는 유사한 도구를 사용하십시오. 어떤 시점에서 실패 할 것이라는 가정보다는 실제 프로토 타입을 확인하여 사용자에게 실패합니다.
Michał Górny

2
@ MichałGórny : 좋은 지적입니다. 솔직히이 질문에서 벗어나야합니다. 나는 그것에 아무것도 기여할 수없는 것 같습니다.
Linuxios

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**하면 실패합니다)
David Rodríguez-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*, 그것은 동일하게 설정 *foo, 호출 iconv다음에 결과 다시 복사, 임시 포인터로 *fooconst_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;
}

이것은 모든 iconv것이 inbuf그 안에 저장된 포인터를 증가시키기 때문에 const-correctness의 POV로부터 안전 합니다. 그래서 우리는 처음 보았을 때 상수가 아닌 포인터에서 파생 된 포인터에서 "const를 캐스팅"하고 있습니다.

우리는 또한 overload of myconvand myconv_helperthat take const char **inbufand messes something about the other direction, 그래서 호출자가 a const char**또는 char**. iconvC ++에서 처음에 호출자에게 주어 졌어 야했지만, 물론 인터페이스는 함수 오버로딩이없는 C에서 복사됩니다.


"super-pedantry"코드는 필요하지 않습니다. 현재 stdlibc ++를 사용하는 GCC4.7 에서 컴파일 하려면 이것이 필요 합니다.
콘라드 루돌프

1

업데이트 : 이제 autotools없이 C ++로 처리 할 수 ​​있다는 것을 알지만, 찾는 사람들을 위해 autoconf 솔루션을 남겨두고 있습니다.

당신이 찾고 iconv.m4있는 것은 gettext 패키지에 의해 설치된 것입니다.

AFAICS는 다음과 같습니다.

AM_ICONV

configure.ac에서 올바른 프로토 타입을 감지해야합니다.

그런 다음 사용하는 코드에서 :

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

이를 위해 템플릿 전문화를 사용하십시오. 위 참조.
Alex

1
감사! 나는 이미 autotools를 사용하고 있으며 이것이 문제를 해결하는 표준 방법으로 보이므로 완벽해야합니다! 불행히도 iconv.m4 파일을 찾기 위해 autoconf를 얻을 수 없었기 때문에 (고대 버전의 autotools가있는 OS X에는 존재하지 않는 것 같습니다) 이식 가능하게 작동 할 수 없었습니다. . 인터넷 검색은 많은 사람들이이 매크로에 문제가 있음을 보여줍니다. 오, autotools!
ridiculous_fish

내 대답에 추악하지만 위험하지 않은 해킹이 있다고 생각합니다. 이미 autoconf를를 사용하는 경우 필요한 설정이 관심있는 플랫폼에 존재하는 경우 그러나, 그리고, 이것을 사용하지 않는 진짜 이유는 ... 없다
스티브 Jessop

내 시스템에서 .m4 파일은 gettext패키지 로 설치됩니다 . 또한 패키지가 m4/디렉토리에 사용 된 매크로를 포함 ACLOCAL_AMFLAGS = -I m4하고 Makefile.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.