Làm cách nào để tôi có thể gọi một hàm C ++ có hàm char ** trên một số nền tảng và const char ** trên một số nền tảng khác?


91

Trên các máy Linux (và OS X) của tôi, iconv()hàm có nguyên mẫu này:

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

trong khi trên FreeBSD, nó trông như thế này:

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

Tôi muốn mã C ++ của mình được xây dựng trên cả hai nền tảng. Với trình biên dịch C, đi qua một char**cho một const char**tham số (hoặc ngược lại) thường phát ra một cảnh báo đơn thuần; tuy nhiên trong C ++, đó là một lỗi nghiêm trọng. Vì vậy, nếu tôi vượt qua a char**, nó sẽ không biên dịch trên BSD và nếu tôi vượt qua a, const char**nó sẽ không biên dịch trên Linux / OS X. Làm cách nào tôi có thể viết mã biên dịch trên cả hai mà không cần cố gắng phát hiện nền tảng?

Một ý tưởng (thất bại) mà tôi có là cung cấp một nguyên mẫu cục bộ ghi đè bất kỳ nguyên mẫu nào được cung cấp bởi tiêu đề:

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

Điều này không thành công vì iconvcần liên kết C và bạn không thể đặt extern "C"trong một hàm (tại sao không?)

Ý tưởng hoạt động tốt nhất mà tôi nghĩ ra là truyền chính con trỏ hàm:

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

nhưng điều này có khả năng che giấu các lỗi khác, nghiêm trọng hơn.


31
Câu hỏi đầu tiên của bạn trên SO. :)
Almo

24
Ghi lại một lỗi tại FreeBSD. Việc triển khai POSIX của iconvyêu cầu inbufkhông phải là hằng số.
dreamlax

3
Truyền chức năng như vậy là không di động.
Jonathan Grynspan

2
@dreamlax: gửi báo cáo lỗi không có khả năng ảnh hưởng; phiên bản hiện tại của FreeBSD dường như đã có iconvmà không có const: svnweb.freebsd.org/base/stable/9/include/…
Fred Foo

2
@larsmans: Thật tốt khi biết! Tôi chưa bao giờ sử dụng FreeBSD nhưng thật tốt khi biết phiên bản mới nhất hỗ trợ tiêu chuẩn mới nhất.
dreamlax

Câu trả lời:


57

Nếu những gì bạn muốn chỉ là làm ngơ trước một số vấn đề về const, thì bạn có thể sử dụng một chuyển đổi làm mờ sự khác biệt, tức là làm cho char ** và const char ** có thể tương tác:

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

Sau đó trong chương trình:

iconv(c, sloppy<char**>(&in) ,&inlen, &out,&outlen);

cẩu thả () nhận a char**hoặc a const char*và chuyển nó thành a char**hoặc a const char*, bất kỳ tham số thứ hai của iconv yêu cầu.

CẬP NHẬT: được thay đổi để sử dụng const_cast và gọi cẩu thả không phải là như đúc.


Điều này hoạt động khá tốt và có vẻ an toàn và đơn giản mà không yêu cầu C ++ 11. Tôi sẽ đi với nó! Cảm ơn!
ridiculous_fish

2
Như tôi đã nói trong câu trả lời của tôi, tôi nghĩ rằng điều này vi phạm răng cưa chặt chẽ trong C ++ 03, vì vậy trong ý nghĩa đó nó không đòi hỏi C ++ 11. Tuy nhiên, tôi có thể sai, nếu ai đó muốn bảo vệ điều này.
Steve Jessop

1
Vui lòng không khuyến khích phôi kiểu C trong C ++; trừ khi tôi sai, bạn có thể gọi sloppy<char**>()trực tiếp trình khởi tạo ở đó.
Michał Górny,

Tất nhiên, nó vẫn hoạt động giống như kiểu ép kiểu C, nhưng sử dụng cú pháp C ++ thay thế. Tôi đoán nó có thể không khuyến khích người đọc sử dụng kiểu C trong các tình huống khác. Ví dụ, cú pháp C ++ sẽ không hoạt động đối với kiểu ép kiểu (char**)&intrừ khi bạn tạo typedef cho char**.
Steve Jessop,

Hack tốt. Để đầy đủ, bạn có thể làm cho điều này (a) luôn nhận const char * const *, giả sử biến không được phép thay đổi hoặc (b) tham số hóa bởi bất kỳ hai kiểu nào và làm cho nó được ép kiểu const giữa chúng.
Jack V.

33

Bạn có thể phân biệt giữa hai khai báo bằng cách kiểm tra chữ ký của hàm đã khai báo. Đây là một ví dụ cơ bản về các mẫu cần thiết để kiểm tra loại tham số. Điều này có thể dễ dàng được khái quát hóa (hoặc bạn có thể sử dụng các đặc điểm chức năng của Boost), nhưng điều này đủ để chứng minh giải pháp cho vấn đề cụ thể của bạn:

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

Đây là một ví dụ minh họa hành vi:

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

Khi bạn có thể phát hiện điều kiện của kiểu tham số, bạn có thể viết hai hàm bao bọc gọi iconv: một hàm gọi iconvvới char const**đối số và một hàm gọi iconvvới char**đối số.

Vì nên tránh chuyên môn hóa mẫu hàm, chúng tôi sử dụng mẫu lớp để thực hiện chuyên môn hóa. Lưu ý rằng chúng tôi cũng làm cho mỗi cái gọi là một mẫu hàm, để đảm bảo rằng chỉ chuyên môn mà chúng tôi sử dụng mới được khởi tạo. Nếu trình biên dịch cố gắng tạo mã cho chuyên môn sai, bạn sẽ gặp lỗi.

Sau đó, chúng tôi kết hợp việc sử dụng chúng với một call_iconvđể làm cho việc gọi này đơn giản như gọi iconvtrực tiếp. Sau đây là một mẫu chung cho thấy cách viết này:

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 */);
}

(Logic thứ hai này có thể được làm sạch và khái quát hóa; tôi đã cố gắng làm rõ ràng từng phần của nó để hy vọng làm rõ hơn cách thức hoạt động của nó.)


3
Phép thuật hay đấy. :) Tôi ủng hộ vì có vẻ như nó trả lời câu hỏi, nhưng tôi chưa xác minh rằng nó hoạt động và tôi không biết đủ C ++ cứng để biết nó có hoạt động hay không chỉ bằng cách nhìn vào nó. :)
Almo

7
Lưu ý: decltypeyêu cầu C ++ 11.
Michał Górny

1
+1 Lol ... vì vậy để tránh #ifdefkiểm tra nền tảng mà bạn kết thúc với 30 dòng mã lẻ :) Mặc dù vậy, cách tiếp cận tốt (mặc dù tôi đã lo lắng trong vài ngày qua khi xem các câu hỏi trên SO rằng những người không thực sự hiểu những gì họ đang làm đã bắt đầu sử dụng SFINAE như một cái búa vàng ... không trường hợp của bạn, nhưng tôi sợ mã sẽ trở nên phức tạp hơn và khó có thể duy trì ...)
David Rodríguez - dribeas

11
@ DavidRodríguez-dribeas: :-) Tôi chỉ tuân theo quy tắc vàng của C ++ hiện đại: nếu thứ gì đó không phải là mẫu, hãy tự hỏi "tại sao đây không phải là mẫu?" sau đó biến nó thành một khuôn mẫu.
James McNellis

1
[Trước khi bất cứ ai xem nhận xét cuối cùng đó một cách quá nghiêm túc: đó là một trò đùa. Đại loại ...]
James McNellis

11

Bạn có thể sử dụng như sau:

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

Bạn có thể vượt qua const char**và trên Linux / OSX, nó sẽ đi qua chức năng mẫu và trên FreeBSD, nó sẽ chuyển trực tiếp đến iconv.

Hạn chế: nó sẽ cho phép các cuộc gọi như iconv(foo, 2.5)vậy sẽ đặt trình biên dịch trong tình trạng lặp lại vô hạn.


2
Đẹp! Tôi nghĩ rằng giải pháp này có tiềm năng: Tôi thích sử dụng độ phân giải quá tải để chọn mẫu chỉ khi chức năng không khớp chính xác. Để làm việc, tuy nhiên, const_castsẽ cần phải được chuyển thành một add_or_remove_constngười đào bới vào T**để phát hiện xem Tconstvà thêm hoặc trình độ loại bỏ cho phù hợp. Điều này vẫn sẽ đơn giản hơn nhiều so với giải pháp mà tôi đã chứng minh. Với một chút công việc, bạn cũng có thể làm cho giải pháp này hoạt động mà không cần const_cast(tức là bằng cách sử dụng một biến cục bộ trong của bạn iconv).
James McNellis

Tôi đã bỏ lỡ một cái gì đó? Trong trường hợp giá trị thực iconvkhông phải là const, không được Tsuy luận là const char**, có nghĩa là tham số inbufcó kiểu const T, nghĩa là const char **const, và lệnh gọi đến iconvtrong khuôn mẫu chỉ gọi chính nó? Giống như James đã nói, với một sửa đổi phù hợp cho loại hình T, thủ thuật này là cơ sở của một thứ hoạt động.
Steve Jessop,

Giải pháp tuyệt vời, thông minh. +1!
Linuxios

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

Ở đây bạn có id của tất cả các hệ điều hành. Đối với tôi, không có ích gì khi thử làm điều gì đó phụ thuộc vào hệ điều hành mà không kiểm tra hệ thống này. Nó giống như mua một chiếc quần màu xanh lá cây nhưng không nhìn vào chúng.


13
Nhưng người hỏi nói một cách rõ ràng without resorting to trying to detect the platform...
Frédéric Hamidi

1
@Linuxios: cho đến khi các nhà cung cấp Linux hoặc Apple quyết định họ làm muốn làm theo các tiêu chuẩn POSIX . Loại mã hóa này nổi tiếng là khó duy trì.
Fred Foo

2
@larsmans: Linux và Mac OS X làm theo tiêu chuẩn . Liên kết của bạn là từ năm 1997. Đó là FreeBSD đằng sau.
dreamlax

3
@Linuxios: Không, nó không [tốt hơn]. Nếu bạn thực sự muốn kiểm tra nền tảng, hãy sử dụng autoconf hoặc một công cụ tương tự. Kiểm tra nguyên mẫu thực tế thay vì thực hiện các giả định sẽ thất bại vào một lúc nào đó và nó sẽ thất bại đối với người dùng.
Michał Górny

2
@ MichałGórny: Điểm tốt. Thành thật mà nói, tôi chỉ nên thoát ra khỏi câu hỏi này. Tôi dường như không thể đóng góp gì cho nó.
Linuxios

1

Bạn đã chỉ ra rằng việc sử dụng chức năng trình bao bọc của riêng bạn là chấp nhận được. Bạn dường như cũng sẵn sàng sống với những lời cảnh báo.

Vì vậy, thay vì viết trình bao bọc của bạn bằng C ++, hãy viết nó bằng C, nơi bạn sẽ chỉ nhận được cảnh báo trên một số hệ thống:

// 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

Làm thế nào về

static void Test(char **)
{
}

int main(void)
{
    const char *t="foo";
    Test(const_cast<char**>(&t));
    return 0;
}

CHỈNH SỬA: tất nhiên, "không phát hiện nền tảng" là một chút vấn đề. Giáo sư :-(

EDIT 2: ok, phiên bản cải tiến, có thể?

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

Vấn đề với đó là trên nền tảng khác, nó sẽ không biên dịch (ví dụ: nếu chức năng mất const char**nó sẽ thất bại)
David Rodríguez - dribeas

1

Thế còn:

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

Tôi nghĩ rằng điều này vi phạm bí danh nghiêm ngặt trong C ++ 03, nhưng không phải trong C ++ 11 vì trong C ++ 11 const char**char**được gọi là "kiểu tương tự". Bạn sẽ không tránh khỏi vi phạm bí danh nghiêm ngặt đó ngoài việc tạo const char*, đặt nó bằng *foo, gọi iconvbằng con trỏ đến tạm thời, sau đó sao chép kết quả trở lại *foosau 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;
}

Điều này là an toàn với POV của hằng số đúng, bởi vì tất cả những gì iconvxảy ra với inbuflà tăng con trỏ được lưu trữ trong đó. Vì vậy, chúng tôi đang "loại bỏ const" khỏi một con trỏ bắt nguồn từ một con trỏ không phải là const khi chúng tôi nhìn thấy nó lần đầu tiên.

Chúng tôi cũng có thể viết một tình trạng quá tải của myconvmyconv_helperrằng mất const char **inbufvà messes điều về theo một hướng khác, do đó người gọi có sự lựa chọn xem có nên vượt qua trong một const char**hoặc một char**. Điều được cho là đáng lẽ iconvphải được cung cấp cho người gọi ngay từ đầu trong C ++, nhưng tất nhiên giao diện chỉ được sao chép từ C mà không có chức năng quá tải.


Mã "super-pedantry" là không cần thiết. Trên GCC4.7 với stdlibc ++ hiện tại, bạn cần nó để biên dịch.
Konrad Rudolph,

1

Cập nhật: bây giờ tôi thấy rằng có thể xử lý nó trong C ++ mà không cần công cụ tự động, nhưng tôi đang để giải pháp autoconf cho những người đang tìm kiếm nó.

Những gì bạn đang tìm kiếm iconv.m4được cài đặt bởi gói gettext.

AFAICS chỉ là:

AM_ICONV

trong config.ac, và nó sẽ phát hiện đúng nguyên mẫu.

Sau đó, trong mã bạn sử dụng:

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

sử dụng chuyên môn hóa mẫu cho điều đó. xem ở trên.
Alex

1
Cảm ơn! Tôi đã sử dụng công cụ tự động và đây có vẻ là cách tiêu chuẩn để giải quyết vấn đề, vì vậy nó phải hoàn hảo! Rất tiếc, tôi không thể lấy autoconf để tìm tệp iconv.m4 (và nó dường như không tồn tại trên OS X, có phiên bản tự động cổ điển), vì vậy tôi không thể làm cho nó hoạt động một cách linh hoạt . Tìm kiếm xung quanh cho thấy rất nhiều người gặp sự cố với macro này. Ồ, ô tô tự động!
ridiculous_fish

Tôi nghĩ rằng tôi có một bản hack xấu xí nhưng không mạo hiểm trong câu trả lời của mình. Tuy nhiên, nếu bạn đã sử dụng autoconf, và nếu cấu hình cần thiết tồn tại trên nền tảng mà bạn quan tâm, không có lý do thực sự không sử dụng này ...
Steve Jessop

Trên hệ thống của tôi, tệp .m4 được cài đặt theo gettextgói. Ngoài ra, nó là khá phổ biến cho các gói bao gồm các macro đã sử dụng trong m4/thư mục và có ACLOCAL_AMFLAGS = -I m4trong đó Makefile.am. Tôi nghĩ rằng autopoint thậm chí còn sao chép nó vào thư mục đó theo mặc định.
Michał Gorny

0

Tôi đến bữa tiệc này muộn nhưng vẫn còn, đây là giải pháp của tôi:

// 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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.