मैं कुछ प्लेटफ़ॉर्म पर चार ** और दूसरों पर एक कास्ट ** करने के लिए C ++ फ़ंक्शन को आंशिक रूप से कैसे कॉल कर सकता हूं?


91

मेरे लिनक्स (और OS X) मशीनों पर, iconv()फ़ंक्शन में यह प्रोटोटाइप है:

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

FreeBSD के दौरान ऐसा दिखता है:

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

मैं अपने C ++ कोड को दोनों प्लेटफार्मों पर बनाना चाहूंगा। सी संकलक के साथ, char**एक const char**पैरामीटर (या इसके विपरीत) के लिए गुजरना आम तौर पर एक मात्र चेतावनी का उत्सर्जन करता है; हालाँकि C ++ में यह एक घातक त्रुटि है। इसलिए यदि मैं पास करता हूं char**, तो यह बीएसडी पर संकलित नहीं करेगा, और यदि मैं const char**इसे पास करता हूं तो यह लिनक्स / ओएस एक्स पर संकलित नहीं होगा। मैं प्लेटफ़ॉर्म का पता लगाने की कोशिश किए बिना, दोनों पर संकलित कोड कैसे लिख सकता हूं?

एक (असफल) विचार मुझे एक स्थानीय प्रोटोटाइप प्रदान करना था जो हेडर द्वारा प्रदान किए गए किसी भी ओवरराइड करता है:

void myfunc(void) {
    size_t iconv (iconv_t, char **inbuf);
    iconv(foo, ptr);
}

यह विफल रहता है क्योंकि iconvसी लिंकेज की जरूरत है, और आप extern "C"एक फ़ंक्शन के भीतर नहीं डाल सकते हैं (क्यों नहीं?)

सबसे अच्छा काम करने का विचार जो मैं आया हूं, वह फ़ंक्शन पॉइंटर को स्वयं डालना है:

typedef void (*func_t)(iconv_t, const char **);
((func_t)(iconv))(foo, ptr);

लेकिन इसमें अन्य, अधिक गंभीर त्रुटियों को मुखौटा करने की क्षमता है।


31
एसओ पर अपने पहले के लिए एक सवाल का नरक। :)
आलमो

24
FreeBSD पर बग दर्ज करें। POSIX कार्यान्वयन iconvको inbufगैर-कॉन्स्टेबल होने की आवश्यकता है ।
ड्रीमलैक्स

3
फ़ंक्शन को इस तरह से कास्टिंग करना गैर-पोर्टेबल है।
जोनाथन ग्राईस्पैन

2
@dreamlax: बग रिपोर्ट सबमिट करने की संभावना नहीं है; FreeBSD का वर्तमान संस्करण iconvबिना जाहिरा तौर पर पहले से ही है const: svnweb.freebsd.org/base/stable/9/include/…
फ्रेड फू

2
@ लार्समैन: यह जानना अच्छा है! मैंने FreeBSD का उपयोग कभी नहीं किया है लेकिन नवीनतम संस्करण को जानने के लिए अच्छा है नवीनतम मानक का समर्थन करता है।
ड्रीमलैक्स

जवाबों:


57

यदि आप चाहते हैं कि बस कुछ कास्ट मुद्दों के लिए एक अंधे आंख को मोड़ना है, तो आप एक रूपांतरण का उपयोग कर सकते हैं जो अंतर को धुंधला करता है, अर्थात् चार ** और कास्ट चार ** इंटरऑपरेबल बनाता है:

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);

मैला () एक char**या एक लेता है const char*और इसे एक या एक में रूपांतरित करता char**है const char*, जो भी आइकनव का दूसरा पैरामीटर मांगता है।

अद्यतन: const_cast का उपयोग करने के लिए बदल दिया और मैला नहीं एक कलाकार के रूप में कहते हैं।


यह काफी अच्छी तरह से काम करता है, और सी ++ 11 की आवश्यकता के बिना सुरक्षित और सीधा लगता है। मैं इसके साथ जा रहा हूँ! धन्यवाद!
हास्यास्पद

2
के रूप में मैं अपने जवाब में कहा, मैं इस सी ++ 03 में सख्त अलियासिंग का उल्लंघन करता है लगता है, तो उस अर्थ में यह करता है सी ++ 11 की आवश्यकता है। हालांकि मैं गलत हो सकता हूं, अगर कोई भी इसका बचाव करना चाहता है।
स्टीव जेसोप

1
कृपया C ++ में C- शैली के कलाकारों को प्रोत्साहित न करें; जब तक मैं गलत नहीं हूं, आप sloppy<char**>()सीधे इनिशियलाइज़र को वहां बुला सकते हैं ।
गोरी

यह अभी भी सी-स्टाइल कास्ट के रूप में एक ही ऑपरेशन है, लेकिन वैकल्पिक सी ++ सिंटैक्स का उपयोग करना। मुझे लगता है कि यह अन्य स्थितियों में सी-शैली के कलाकारों का उपयोग करने से पाठकों को हतोत्साहित कर सकता है। उदाहरण के लिए C ++ सिंटैक्स कलाकारों के लिए (char**)&inतब तक काम नहीं करेगा जब तक कि आप पहले इसके लिए एक टाइपराइफ़ नहीं बनाते हैं char**
स्टीव जेसोप

अच्छा हैक। पूर्णता के लिए, आप शायद इसे या तो (ए) हमेशा एक कास्ट चार * कास्ट * ले सकते हैं, यह मानते हुए कि वेरिएबल को किसी भी दो प्रकार से परिवर्तित नहीं किया जा सकता है या (बी) इसे उनके बीच कास्ट कास्ट बना सकता है।
जैक वी।

33

आप घोषित फ़ंक्शन के हस्ताक्षर का निरीक्षण करके दो घोषणाओं के बीच में अंतर कर सकते हैं। यहां पैरामीटर प्रकार का निरीक्षण करने के लिए आवश्यक टेम्प्लेट का एक मूल उदाहरण दिया गया है। इसे आसानी से सामान्यीकृत किया जा सकता है (या आप बूस्ट के कार्य लक्षणों का उपयोग कर सकते हैं), लेकिन यह आपके विशिष्ट कार्य के समाधान का प्रदर्शन करने के लिए पर्याप्त है:

#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एक 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 ++ नहीं पता है कि क्या यह सिर्फ इसे देखकर है। :)
आलमो

7
नोट के रूप में: decltypeC ++ 11 की आवश्यकता है।
मीकल गोरी

1
+1 LOL ... इसलिए #ifdefआप जिस प्लेटफ़ॉर्म की 30 विषम पंक्तियों के साथ समाप्त होते हैं, उसके लिए एक चेकिंग से बचने के लिए :) अच्छा तरीका हालांकि (हालांकि मैं पिछले कुछ दिनों से चिंता कर रहा हूं ताकि एसओ के सवालों को देख सकें जो लोग नहीं करते हैं वास्तव में समझते हैं कि वे क्या कर रहे हैं SFINAE को एक सुनहरे हथौड़े के रूप में इस्तेमाल करना शुरू कर दिया है ... आपके मामले में नहीं, लेकिन मुझे डर है कि कोड अधिक जटिल और बनाए रखने के लिए कठिन हो जाएगा ...)
डेविड रोड्रिग्ज़ - dribeas

11
@ DavidRodríguez-dribeas: :-) मैं सिर्फ आधुनिक C ++ के सुनहरे नियम का पालन करता हूं: यदि कोई चीज टेम्पलेट नहीं है, तो अपने आप से पूछें, "यह कोई टेम्पलेट क्यों नहीं है?" फिर इसे एक टेम्प्लेट बनाएं।
जेम्स मैकनेलिस

1
[इससे पहले कि कोई भी उस अंतिम टिप्पणी को भी गंभीरता से लेता है: यह एक मजाक है। क्रमबद्ध करें ...]
जेम्स मैकनेलिस

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**और लिनक्स / ओएसएक्स पर यह टेम्प्लेट फ़ंक्शन के माध्यम से जाएगा और फ्रीबीएसडी पर यह सीधे जाएगा iconv

ड्राबैक: यह उन कॉलों को अनुमति देगा, iconv(foo, 2.5)जो संकलक को अनंत पुनरावृत्ति में डाल देंगे।


2
अच्छा! मुझे लगता है कि इस समाधान में क्षमता है: मुझे टेम्पलेट का चयन करने के लिए अधिभार संकल्प का उपयोग करना पसंद है, जब फ़ंक्शन एक सटीक मिलान नहीं है। हालांकि, काम करने के लिए, यह पता लगाने के लिए कि खोदता है और उचित के रूप में योग्यता को जोड़ना या निकालना है , उस const_castमें ले जाने की आवश्यकता होगी । यह मेरे द्वारा दिखाए गए समाधान की तुलना में अभी भी (अधिक) सीधा होगा। थोड़े से काम के साथ, इस समाधान को बिना (यानी, आपके स्थानीय चर का उपयोग करके ) बनाना संभव हो सकता है । add_or_remove_constT**Tconstconst_casticonv
जेम्स मैकनेलिस

क्या मुझे कुछ याद आया? उस मामले में जहां वास्तविक iconvगैर-कास्ट है, के Tरूप में कटौती नहीं की जाती है const char**, जिसका अर्थ है कि पैरामीटर inbufमें टाइप है const T, जो है const char **const, और iconvटेम्पलेट में कॉल केवल कॉल करता है? जैसा कि जेम्स कहते हैं, हालांकि Tइस प्रकार के लिए एक उपयुक्त संशोधन के साथ यह चाल काम करने वाली चीज़ का आधार है।
स्टीव जेसप

बहुत बढ़िया, चतुर समाधान। +1!
लिनक्स जूल

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

यहां आपके पास सभी ऑपरेटिंग सिस्टम की आईडी हैं। मेरे लिए यह इस प्रणाली की जाँच के बिना ऑपरेटिंग सिस्टम पर निर्भर करता है कि कुछ करने की कोशिश करने के लिए कोई मतलब नहीं है। यह हरे रंग की पतलून खरीदने जैसा है, लेकिन उन्हें देखे बिना।


13
लेकिन प्रश्नकर्ता स्पष्ट रूप से कहता है without resorting to trying to detect the platform...
Frédéric Hamidi

1
@Linuxios: जब तक लिनक्स विक्रेता या Apple तय नहीं करते कि वे POSIX मानक का पालन करना चाहते हैं । इस तरह की कोडिंग को बनाए रखना बेहद कठिन है।
फ्रेड फू

2
@larsmans: लिनक्स और मैक ओएस एक्स कर मानक का पालन । आपका लिंक 1997 से है। यह FreeBSD है जो पीछे है।
ड्रीमलैक्स

3
@Linuxios: नहीं, यह [बेहतर] नहीं है। यदि आप वास्तव में प्लेटफ़ॉर्म चेक करना चाहते हैं, तो ऑटोकॉनफ़ या इसी तरह के उपकरण का उपयोग करें। मान्यताओं को करने के बजाय वास्तविक प्रोटोटाइप की जांच करें जो किसी बिंदु पर विफल हो जाएगा, और यह उपयोगकर्ता पर विफल हो जाएगा।
माइकल गोरी

2
@ मिचलोग्रॉनी: अच्छी बात है। सच कहूं, तो मुझे इस सवाल से बाहर निकलना चाहिए। मैं इसके लिए कुछ भी योगदान करने में सक्षम नहीं लगता।
20

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;
}

EDIT: बेशक, "प्लेटफ़ॉर्म का पता लगाए बिना" थोड़ी समस्या है। उफ़ :-(

संपादित करें 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**तो यह विफल हो जाएगा)
डेविड रोड्रिग्ज़ - 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, अस्थायी के लिए एक पॉइंटर के साथ कॉल करें , फिर परिणाम को *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;
}

यह कब्ज की शुद्धता से सुरक्षित है, क्योंकि सभी के iconvसाथ हैinbuf संचित पॉइंटर को बढ़ाता है। इसलिए हम एक पॉइंटर से "पॉस्ट दूर कास्ट" कर रहे हैं जो एक पॉइंटर से निकला था जो तब गैर-कास्ट था जब हमने पहली बार देखा था।

हम एक अधिभार को भी लिख सकते हैं myconvऔर myconv_helperजो const char **inbufदूसरी दिशा में चीजों को ले जाते हैं और गड़बड़ करते हैं, ताकि कॉल करने वाले के पास यह विकल्प हो कि उसे पास करना है const char**या नहीं char**। यकीनन iconvC ++ में पहले स्थान पर कॉल करने वाले को दिया जाना चाहिए, लेकिन निश्चित रूप से इंटरफ़ेस को C से कॉपी किया गया है जहां कोई फ़ंक्शन ओवरलोडिंग नहीं है।


"सुपर-पेडेंट्री" कोड अनावश्यक है। वर्तमान stdlibc ++ के साथ GCC4.7 पर, आपको यह संकलन करने की आवश्यकता है
कोनराड रूडोल्फ

1

अपडेट: अब मैं देखता हूं कि ऑटोटूल के बिना सी ++ में इसे संभालना संभव है, फिर भी मैं इसे देखने वाले लोगों के लिए ऑटोकॉन्फ़ समाधान छोड़ रहा हूं।

तुम जो खोज रहे हो iconv.m4 जो गेटटेक्स्ट पैकेज द्वारा स्थापित है।

AFAICS यह सिर्फ है:

AM_ICONV

config.ac में, और इसे सही प्रोटोटाइप का पता लगाना चाहिए।

फिर, आपके द्वारा उपयोग किए गए कोड में:

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

उसके लिए टेम्पलेट विशेषज्ञता का उपयोग करें। ऊपर देखो।
एलेक्स

1
धन्यवाद! मैं पहले से ही ऑटोटूल का उपयोग करता हूं, और यह मुद्दे के चारों ओर काम करने का मानक तरीका प्रतीत होता है, इसलिए इसे पूर्ण होना चाहिए! दुर्भाग्य से मैं iconv.m4 फ़ाइल खोजने के लिए ऑटोकॉनफ़ प्राप्त करने में सक्षम नहीं था (और यह ओएस एक्स पर मौजूद नहीं है, जिसमें ऑटोटूल का एक प्राचीन संस्करण है), इसलिए मैं इसे आंशिक रूप से काम करने में सक्षम नहीं था । आसपास घूमने से पता चलता है कि बहुत से लोगों को इस मैक्रो से परेशानी है। ओह, ऑटोटूल!
हास्यास्पद_फिशल

मुझे लगता है कि मेरे जवाब में एक बदसूरत लेकिन गैर-जोखिम भरा हैक है। फिर भी, यदि आप पहले से ही ऑटोकॉन्फ़ का उपयोग कर रहे हैं, और यदि आवश्यक कॉन्फ़िगर उस प्लेटफ़ॉर्म पर मौजूद है जिसकी आप परवाह करते हैं, तो इसका उपयोग न करने का कोई वास्तविक कारण नहीं है ...
स्टीव जेसप

मेरे सिस्टम पर .m4 फ़ाइल gettextपैकेज द्वारा स्थापित है । इसके अलावा, संकुल के लिए m4/निर्देशिका में मैक्रोज़ का उपयोग करना और इसमें शामिल होना काफी आम ACLOCAL_AMFLAGS = -I m4है Makefile.am। मुझे लगता है कि ऑटोपॉइंट इसे डिफ़ॉल्ट रूप से उस निर्देशिका में भी कॉपी करता है।
मीकल गोरी

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);
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.