2013-05-15 12 views
11

Nói chung, việc sử dụng tính năng mẫu C++ 11 variadic có chức năng yêu cầu đối số hàm dựa trên variadic là đối số cuối cùng trong danh sách đối số hàm. Có một ngoại lệ; chúng là các đối số từ đầu đến cuối nếu có các đối số variadic C-level, mà phải chết cuối cùng.Làm thế nào các đối số C++ và C variadic có thể được sử dụng cùng nhau?

template < typename ...Args > 
int super_printf(Something x, Args &&...a, ...); 

Đôi khi tôi nghĩ ngẫu nhiên về C++ và tôi tự hỏi làm thế nào một chức năng như vậy có thể được triển khai. Lần đầu tiên tôi nghĩ về việc bóc lột đệ quy thông thường từ a, sau đó tôi nhớ rằng các varargs cấp C không xếp tầng. Tôi phải biến chúng thành một va_list dứt khoát ngay lập tức.

template < typename ...Args > 
int super_vaprintf(Something x, std::va_list &aa, Args &&...a); 
// Note that "aa" is passed by reference. 

template < typename ...Args > 
int super_printf(Something x, Args &&...a, ...) 
{ 
    std::va_list args2; 
    int   result; 

    va_start(args2, XXX); // (A) 
    try { 
     result = super_vaprintf(x, args2, std::forward<Args>(a)...); 
    } catch (...) { 
     va_end(args2); // (1) 
     throw; 
    } 
    va_end(args2); // (2) 
    return result; 

    // Can (1) and (2) be compacted with RAII using a custom deleter lambda 
    // in std::unique_ptr or something? Remember that "va_end" is a macro! 
} 

Việc lột bỏ thông thường C++ variadic xảy ra trong cuộc gọi super_vaprintf. Tại dòng (A), những gì xảy ra ở vị trí của XXX, "a" hoặc "a ..."? Điều gì sẽ xảy ra nếu a trống, thay vào đó, x sẽ chuyển sang đó? Nếu câu hỏi cuối cùng đó là đúng, chúng tôi sẽ bị hỏng nếu không có x; rằng không có đối số bên cạnh những cái variadic? (Và nếu đó là sự thật, làm thế nào để chúng ta conditionalize mã để sử dụng x khi một là trống rỗng, và một khác?)

...

Tôi chỉ nhìn bản sao của tôi về C++ 11 tiêu chuẩn cho bất kỳ hỗ trợ ở đây. Dường như không có gì. Điều này sẽ nhắc một yêu cầu cho ủy ban C++ quay trở lại để sửa lỗi này, nhưng tôi không chắc rằng có bất kỳ cách nào một hàm như vậy có thể được gọi mà không có biến thể C++ lấy mọi thứ. Liệu tôi có sai; có thể thực hiện một cuộc gọi hàm để sử dụng cả hai biến thể C++ và C? Hoặc là chỉ pha trộn hữu ích cho các khai báo, về các thủ thuật instantiation Stupid (Template)?

+0

Chỉ sau khi đăng bài này, tôi nhận ra rằng có thể một cái gì đó như "' super_printf (Cái gì {}, 1, 2, 3, 4) '" có thể bắt buộc một số đối số là cấp C ("3" và "4" trong trường hợp này). – CTMacUser

+1

chúng là trực giao, vì c và hoạt động ở thời gian chạy, và C++ Các mẫu variadic làm việc tại thời gian biên dịch –

+0

@CTMacUser: vâng, điều đó sẽ hoạt động. Câu hỏi vẫn còn hữu ích như thế nào, và tôi sẽ nói trong metaprogramming nó có thể . có thể bạn có thể tìm thấy nhiều thông tin chi tiết hơn trong việc đọc các đề xuất mẫu của variadic, họ có thể cho biết lý do tại sao điều này là có thể. – PlasmaHH

Trả lời

0

Tôi đã thử mã này trên trang web biên dịch (Coliru), với GCC 4.8 và kết quả trông ảm đạm. Tôi không biết nếu nó là GCC nói riêng, hoặc nếu tất cả các trình biên dịch khác ra có làm một cái gì đó tương tự. Vì vậy, những người có thể với các trình biên dịch khác (Clang, Visual C++, Intel, vv) thử này?

#include <cstdarg> 
#include <iostream> 
#include <ostream> 
#include <utility> 

template < typename ...Args > 
int super_vaprintf(long, std::va_list &, Args &&...) 
{ 
    return 17; 
} 

template < typename ...Args > 
int super_printf(long x, Args &&...a, ...) 
{ 
    std::va_list args2; 
    int   result; 

    va_start(args2, a); // (A) 
    try { 
     result = super_vaprintf(x, args2, std::forward<Args>(a)...); 
    } catch (...) { 
     va_end(args2); 
     throw; 
    } 
    va_end(args2); 
    return result; 
} 

int main() { 
    std::cout << super_printf<int, int>(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 
    return 0; 
} 

Các cuộc gọi đến super_printf trên dòng (B) thiết lập một cách rõ ràng C++ varargs hai int mục. Điều này sẽ làm cho hàm sử dụng đối số 12 dưới dạng biến thể C++ và sau đó là ba varargs C.

Trên dòng (A), trình biên dịch khẳng định rằng mã có a trong đó có một "..." ở đâu đó. Vì vậy, tôi thay đổi nó thành:

va_start(args2, a...); // (A) 

Tôi gặp lỗi khác về việc có sai số đối số. Nó có ý nghĩa vì a mở rộng thành hai đối số. Nếu tôi thay đổi dòng (B) thành một C++ vararg:

std::cout << super_printf<int>(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

nó hoạt động tốt. Nếu tôi loại bỏ các C++ varargs hoàn toàn:

std::cout << super_printf<>(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

chúng tôi nhận được lỗi sai-number-hoặc-luận một lần nữa, vì a có chiều dài không).Nếu chúng ta làm được điều này khi a trống:

va_start(args2, x /*a...*/); // (A) 

mã làm việc một lần nữa, mặc dù có một cảnh báo về x không phải là tham số tên cuối cùng.

Chúng tôi có thể tiếp cận ví dụ theo cách khác. Hãy đặt lại thành:

va_start(args2, a...); // (A) 
//... 
std::cout << super_printf(0L, 1, 2, 3, 4, 5) << std::endl; // (B) 

trong đó tất cả các đối số sau lần đầu tiên được nhóm thành biến thể C++. Chúng tôi nhận được cùng một lỗi quá nhiều đối số trong va_start, tất nhiên. Tôi dần dần nhận xét các đối số theo sau. Nó hoạt động khi có chính xác hai đối số trái (mà làm cho a có chính xác một đối số).

Ngoài ra còn có lỗi khi chỉ còn một đối số, nhưng thông báo lỗi thay đổi để nói rõ ràng "quá ít" đối số thay vì "số tiền sai". Như trước đây, tôi đã tắt "a..." cho "x" trong dòng (A) và mã đã được chấp nhận nhưng không có cảnh báo. Vì vậy, có vẻ như khi tôi rõ ràng bao gồm "<Whatever>" cho super_printf trong dòng (B), tôi nhận được một đường dẫn lỗi phân tích cú pháp khác hơn khi tôi không bao gồm chúng, mặc dù cả hai đường dẫn đi đến cùng một kết luận.

Thời gian để nói với ủy ban rằng họ bị bỏ qua một cái gì đó ....

+0

Về đoạn cuối cùng của bạn ... không, không thực sự. –

+0

@LightnessRacesinOrbit, thực sự [có] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1790). – CTMacUser

+0

: P BTW Coliru hỗ trợ Clang –

4

Khi bạn gọi một hàm có tham số cuối cùng là một gói, tất cả các đối số trở thành một phần của gói đó. Không còn gì cho va_args. Việc bạn sử dụng các đối số mẫu rõ ràng là gây hiểu nhầm vì chúng không độc quyền; chúng đơn giản đứng trước các đối số ngầm định.

Để đánh bại khấu trừ, bạn cần có một tài liệu tham khảo:

(& super_printf<int, int>) (0L, 1, 2, 3, 4, 5) 

này là khá giả tạo, nhưng tại bạn có vấn đề gì để vượt qua va_start.

Để cung cấp giao diện hợp lý cho người dùng, chỉ cần thêm thông số giữa hai danh sách.

struct va_separator {}; // Empty; ABI may elide allocation. 

template < typename ...Args > 
int super_printf(Something x, Args &&...a, va_separator, ...); 

super_printf sẽ cần cả hai đối số rõ ràng để xác định gói và đối số tách biệt rõ ràng. Nhưng bạn có thể cung cấp một hàm công cộng nhận tất cả các đối số của nó bằng gói, sau đó tìm dấu tách và chuyển tiếp đến super_printf bằng danh sách đối số rõ ràng bao gồm các phần tử gói trước dấu phân cách.