ฉันจะเรียกใช้ฟังก์ชัน C ++ แบบพกพาที่ใช้ถ่าน ** บนบางแพลตฟอร์มและ const char ** บนแพลตฟอร์มอื่น ๆ ได้อย่างไร


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 ++ เป็นข้อผิดพลาดร้ายแรง ดังนั้นถ้าฉันผ่าน a 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);

แต่สิ่งนี้อาจปกปิดข้อผิดพลาดอื่น ๆ ที่ร้ายแรงกว่าได้


31
คำถามแรกของคุณเกี่ยวกับ SO :)
Almo

24
บันทึกข้อบกพร่องที่ FreeBSD การนำ POSIX ไปใช้iconvจำเป็นต้องinbufไม่ใช่ const
dreamlax

3
แคสต์ฟังก์ชั่นแบบนั้นไม่พกพา
Jonathan Grynspan

2
@dreamlax: การส่งรายงานข้อผิดพลาดไม่น่าจะมีผล เห็นได้ชัดว่า FreeBSD เวอร์ชันปัจจุบันมีอยู่แล้วiconvโดยไม่มีconst: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo

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*และแปลงเป็น a char**หรือ a const char*ไม่ว่าพารามิเตอร์ที่สองของ iconv ต้องการ

UPDATE: เปลี่ยนไปใช้ const_cast และโทรชุ่ยไม่ใช่เป็นแคสต์


วิธีนี้ใช้งานได้ดีและดูเหมือนจะปลอดภัยและตรงไปตรงมาโดยไม่ต้องใช้ C ++ 11 ฉันจะไปกับมัน! ขอบคุณ!
ไร้สาระ

2
อย่างที่ฉันพูดในคำตอบของฉันฉันคิดว่าสิ่งนี้ละเมิดนามแฝงที่เข้มงวดใน C ++ 03 ดังนั้นในแง่นั้นจึงต้องใช้ C ++ 11 ฉันอาจจะคิดผิดถ้าใครต้องการปกป้องสิ่งนี้
Steve Jessop

1
โปรดอย่าสนับสนุนการแคสต์รูปแบบ C ใน C ++ เว้นแต่ฉันจะผิดคุณสามารถโทรหาsloppy<char**>()initializer ได้โดยตรงที่นั่น
MichałGórny

มันยังคงดำเนินการเช่นเดียวกับการแคสต์สไตล์ C แต่ใช้ไวยากรณ์ C ++ ทางเลือก ฉันเดาว่ามันอาจจะกีดกันผู้อ่านจากการใช้ C-style casts ในสถานการณ์อื่น ๆ ตัวอย่างเช่น C ++ ไวยากรณ์จะไม่ทำงานให้กับทีมนักแสดง(char**)&inจนกว่าคุณจะเป็นครั้งแรกทำให้ typedef char**สำหรับ
Steve Jessop

แฮ็คที่ดี เพื่อความสมบูรณ์คุณอาจทำให้สิ่งนี้ (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;
}

เมื่อคุณตรวจพบคุณสมบัติของประเภทพารามิเตอร์ได้แล้วคุณสามารถเขียนฟังก์ชัน wrapper สองฟังก์ชันที่เรียกใช้ฟังก์ชันiconvหนึ่งที่เรียกiconvด้วยchar const**อาร์กิวเมนต์และฟังก์ชันหนึ่งที่เรียก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 */);
}

(ตรรกะหลังนี้สามารถทำความสะอาดและสรุปได้ทั่วไปฉันพยายามทำให้แต่ละส่วนชัดเจนเพื่อหวังว่าจะทำให้ชัดเจนขึ้นว่ามันทำงานอย่างไร)


3
วิเศษมากที่นั่น :) ฉันโหวตให้คะแนนเพราะดูเหมือนว่าจะตอบคำถาม แต่ฉันยังไม่ได้ตรวจสอบว่ามันใช้งานได้และฉันไม่รู้จัก C ++ แบบฮาร์ดคอร์มากพอที่จะรู้ว่ามันทำได้โดยการดูหรือไม่ :)
Almo

7
หมายเหตุ: decltypeต้องใช้ C ++ 11
MichałGórny

1
+1 ฮ่า ๆ ... ดังนั้นเพื่อหลีกเลี่ยงการ#ifdefตรวจสอบแพลตฟอร์มที่คุณลงท้ายด้วยรหัส 30 บรรทัดแปลก ๆ :) แม้ว่าวิธีการที่ดี (แม้ว่าฉันจะกังวลในช่วงสองสามวันที่ผ่านมาเพื่อดูคำถามเกี่ยวกับ SO ที่คนที่ไม่ทำ เข้าใจจริงๆว่าพวกเขากำลังทำอะไรอยู่เริ่มใช้ SFINAE เป็นค้อนทองคำ ... ไม่ใช่กรณีของคุณ แต่ฉันกลัวว่าโค้ดจะซับซ้อนขึ้นและดูแลรักษายาก ... )
David Rodríguez - dribeas

11
@ DavidRodríguez-dribeas :-) ฉันแค่ทำตามกฎทองของ C ++ สมัยใหม่: ถ้าบางอย่างไม่ใช่เทมเพลตให้ถามตัวเองว่า "ทำไมนี่ถึงไม่เป็นเทมเพลต" จากนั้นทำให้เป็นเทมเพลต
James McNellis

1
[ก่อนที่ใครจะใช้ความคิดเห็นสุดท้ายนั้นจริงจังเกินไปมันเป็นเรื่องตลก เรียงจาก ... ]
James 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**และลินุกซ์ / OSX มันจะไปถึงฟังก์ชั่นแม่แบบและบน FreeBSD iconvมันจะไปโดยตรงไป

ข้อเสียเปรียบ: จะอนุญาตให้มีการโทรเช่นiconv(foo, 2.5)ซึ่งจะทำให้คอมไพเลอร์เกิดการเกิดซ้ำแบบไม่สิ้นสุด


2
ดี! ฉันคิดว่าโซลูชันนี้มีศักยภาพ: ฉันชอบใช้ความละเอียดเกินพิกัดเพื่อเลือกเทมเพลตเฉพาะเมื่อฟังก์ชันไม่ตรงกันทุกประการ อย่างไรก็ตามในการทำงานconst_castจะต้องย้ายไปยังส่วนadd_or_remove_constที่เจาะลึกลงT**ไปเพื่อตรวจสอบว่าTเป็นconstและเพิ่มหรือลบคุณสมบัติตามความเหมาะสม สิ่งนี้ยังคงตรงไปตรงมามากกว่าวิธีแก้ปัญหาที่ฉันได้แสดง ด้วยการทำงานเล็กน้อยอาจเป็นไปได้ที่จะทำให้โซลูชันนี้ทำงานได้โดยไม่ต้องใช้const_cast(กล่าวคือโดยใช้ตัวแปรโลคัลในของคุณiconv)
James McNellis

ฉันพลาดอะไรไปหรือเปล่า? ในกรณีที่ของจริงiconvไม่ใช่ const จะไม่Tอนุมานตามconst char**ซึ่งหมายความว่าพารามิเตอร์inbufมี type const Tคืออะไรconst char **constและการเรียกไปยังiconvในเทมเพลตจะเรียกตัวเองว่า? เช่นเดียวกับที่เจมส์กล่าวว่าด้วยการปรับเปลี่ยนประเภทที่เหมาะสมTเคล็ดลับนี้เป็นพื้นฐานของสิ่งที่ได้ผล
Steve Jessop

วิธีแก้ปัญหาที่ยอดเยี่ยมและชาญฉลาด +1!
Linuxios

7
#ifdef __linux__
... // linux code goes here.
#elif __FreeBSD__
... // FreeBSD code goes here.
#endif

ที่นี่คุณมีรหัสของระบบปฏิบัติการทั้งหมด สำหรับฉันมันไม่มีจุดที่จะลองทำสิ่งที่ขึ้นอยู่กับระบบปฏิบัติการโดยไม่ตรวจสอบระบบนี้ เหมือนกับการซื้อกางเกงขายาวสีเขียว แต่ไม่ได้มอง


14
แต่ผู้ถามพูดอย่างชัดเจนว่าwithout resorting to trying to detect the platform...
Frédéric Hamidi

1
@Linuxios: จนกว่าผู้ขายลินุกซ์หรือแอปเปิ้ลตัดสินใจว่าพวกเขาไม่ต้องการที่จะปฏิบัติตามมาตรฐาน POSIX การเข้ารหัสแบบนี้เป็นเรื่องยากที่จะรักษา
Fred Foo

2
@larsmans: Linux และ Mac OS X ทำปฏิบัติตามมาตรฐาน ลิงค์ของคุณมาจากปี 1997 เป็น FreeBSD ที่อยู่เบื้องหลัง
dreamlax

3
@Linuxios: ไม่มันไม่ [ดีกว่า] หากคุณต้องการตรวจสอบแพลตฟอร์มจริงๆให้ใช้ autoconf หรือเครื่องมือที่คล้ายกัน ตรวจสอบต้นแบบจริงแทนที่จะตั้งสมมติฐานซึ่งจะล้มเหลวในบางจุดและจะล้มเหลวกับผู้ใช้
MichałGórny

2
@ MichałGórny: จุดดี ตรงไปตรงมาฉันควรจะออกจากคำถามนี้ ดูเหมือนฉันจะไม่สามารถมีส่วนร่วมอะไรได้เลย
Linuxios

1

คุณได้ระบุว่าการใช้ฟังก์ชัน Wrapper ของคุณเองนั้นยอมรับได้ ดูเหมือนว่าคุณเต็มใจที่จะอยู่กับคำเตือน

ดังนั้นแทนที่จะเขียน wrapper ของคุณใน 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**เรียกว่า "ประเภทที่คล้ายกัน" คุณจะไม่ได้ไปเพื่อหลีกเลี่ยงการละเมิด aliasing เข้มงวดอื่น ๆ ที่นอกเหนือจากการสร้าง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;
}

สิ่งนี้ปลอดภัยจาก POV ของความถูกต้องของ const เพราะสิ่งที่iconvทำinbufคือการเพิ่มตัวชี้ที่เก็บไว้ในนั้น ดังนั้นเราจึง "แคสต์ const ออกไป" จากตัวชี้ที่ได้มาจากตัวชี้ที่ไม่ใช่ const เมื่อเราเห็นครั้งแรก

นอกจากนี้เรายังสามารถเขียนเกินพิกัดmyconvและmyconv_helperที่ใช้const char **inbufและทำให้สิ่งต่าง ๆ ยุ่งเหยิงไปในทิศทางอื่นเพื่อให้ผู้โทรมีทางเลือกว่าจะส่งconst char**ไฟล์char**. เนื้อหาใดที่iconvควรให้แก่ผู้โทรตั้งแต่แรกใน C ++ แต่แน่นอนว่าอินเทอร์เฟซนั้นคัดลอกมาจาก C โดยที่ไม่มีฟังก์ชันโอเวอร์โหลด


ไม่จำเป็นต้องใช้รหัส "super-pedantry" บน GCC4.7 ที่มี stdlibc ++ ปัจจุบันคุณต้องมีสิ่งนี้เพื่อคอมไพล์
Konrad Rudolph

1

อัปเดต: ตอนนี้ฉันเห็นว่ามันเป็นไปได้ที่จะจัดการมันใน C ++ โดยไม่ต้องใช้เครื่องมืออัตโนมัติ แต่ฉันกำลังออกจากโซลูชัน autoconf สำหรับผู้ที่กำลังมองหามัน

สิ่งที่คุณกำลังมองหาคือiconv.m4สิ่งที่ติดตั้งโดยแพ็คเกจ gettext

AFAICS เป็นเพียง:

AM_ICONV

ใน config.ac และควรตรวจพบต้นแบบที่ถูกต้อง

จากนั้นในรหัสที่คุณใช้:

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

ใช้ความเชี่ยวชาญเทมเพลตสำหรับสิ่งนั้น ดูด้านบน.
Alexander Oh

1
ขอบคุณ! ฉันใช้เครื่องมืออัตโนมัติอยู่แล้วและดูเหมือนว่านี่จะเป็นวิธีมาตรฐานในการแก้ไขปัญหาดังนั้นจึงควรสมบูรณ์แบบ! น่าเสียดายที่ฉันไม่สามารถรับ autoconf เพื่อค้นหาไฟล์ iconv.m4 ได้ (และดูเหมือนจะไม่มีใน OS X ซึ่งมีเครื่องมืออัตโนมัติเวอร์ชันโบราณ) ดังนั้นฉันจึงไม่สามารถทำให้มันทำงานแบบพกพาได้ . Googling around แสดงให้เห็นว่าผู้คนจำนวนมากมีปัญหากับมาโครนี้ โอ้เครื่องมืออัตโนมัติ!
ไร้สาระ

ฉันคิดว่าฉันมีแฮ็คที่น่าเกลียด แต่ไม่มีความเสี่ยงในคำตอบของฉัน ถึงกระนั้นหากคุณใช้ autoconf อยู่แล้วและหากมีการกำหนดค่าที่จำเป็นบนแพลตฟอร์มที่คุณสนใจก็ไม่มีเหตุผลที่แท้จริงที่จะไม่ใช้สิ่งนี้ ...
Steve Jessop

บนระบบของฉันไฟล์. m4 ถูกติดตั้งโดยgettextแพ็คเกจ นอกจากนี้เป็นเรื่องปกติที่แพคเกจจะรวมมาโครที่ใช้แล้วในm4/ไดเร็กทอรีและมีACLOCAL_AMFLAGS = -I m4ในMakefile.am. ฉันคิดว่าจุดอัตโนมัติจะคัดลอกไปยังไดเรกทอรีนั้นตามค่าเริ่มต้น
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.