Kiểm tra xem một lớp có chức năng thành viên của một chữ ký nhất định


135

Tôi đang yêu cầu một thủ thuật mẫu để phát hiện xem một lớp có chức năng thành viên cụ thể của chữ ký đã cho hay không.

Vấn đề tương tự như vấn đề được trích dẫn ở đây http://www.gotw.ca/gotw/071.htm nhưng không giống nhau: trong mục của cuốn sách của Sutter, ông đã trả lời câu hỏi rằng lớp C PHẢI CUNG CẤP một chức năng thành viên với một chữ ký cụ thể, chương trình khác sẽ không được biên dịch. Trong vấn đề của tôi, tôi cần phải làm một cái gì đó nếu một lớp có chức năng đó, khác thì làm "cái gì khác".

Một vấn đề tương tự đã gặp phải bởi boost :: serialization nhưng tôi không thích giải pháp mà họ đã áp dụng: một hàm mẫu gọi theo mặc định một hàm miễn phí (mà bạn phải xác định) với một chữ ký cụ thể trừ khi bạn xác định một hàm thành viên cụ thể ( trong trường hợp của họ "tuần tự hóa" có 2 tham số của một loại nhất định) với một chữ ký cụ thể, nếu không sẽ xảy ra lỗi biên dịch. Đó là thực hiện cả tuần tự xâm nhập và không xâm nhập.

Tôi không thích giải pháp đó vì hai lý do:

  1. Để không xâm phạm, bạn phải ghi đè chức năng "tuần tự hóa" toàn cầu trong không gian tên boost :: serialization, vì vậy bạn có IN COI CLIENT của bạn để mở tăng không gian tên và tuần tự hóa không gian tên!
  2. Ngăn xếp để giải quyết mớ hỗn độn đó là 10 đến 12 lần gọi hàm.

Tôi cần xác định một hành vi tùy chỉnh cho các lớp không có chức năng thành viên đó và các thực thể của tôi nằm trong các không gian tên khác nhau (và tôi không muốn ghi đè một hàm toàn cục được xác định trong một không gian tên trong khi tôi ở một không gian tên khác)

Bạn có thể cho tôi một gợi ý để giải câu đố này?



@ R.MartinhoFernandes Bạn đang tìm kiếm loại câu trả lời nào? Câu trả lời này của Mike Kinghan đi khá sâu và đang sử dụng công cụ C ++ 11.
jrok

@ R.MartinhoFernandes Có lẽ đây là phiên bản hiện đại mà bạn đang tìm kiếm?
Daniel Frey

Câu trả lời:


90

Tôi không chắc liệu tôi có hiểu đúng về bạn không, nhưng bạn có thể khai thác SFINAE để phát hiện sự hiện diện của chức năng tại thời điểm biên dịch. Ví dụ từ mã của tôi (kiểm tra nếu lớp có hàm thành viên size_t used_memory () const).

template<typename T>
struct HasUsedMemoryMethod
{
    template<typename U, size_t (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::used_memory>*);
    template<typename U> static int Test(...);
    static const bool Has = sizeof(Test<T>(0)) == sizeof(char);
};

template<typename TMap>
void ReportMemUsage(const TMap& m, std::true_type)
{
        // We may call used_memory() on m here.
}
template<typename TMap>
void ReportMemUsage(const TMap&, std::false_type)
{
}
template<typename TMap>
void ReportMemUsage(const TMap& m)
{
    ReportMemUsage(m, 
        std::integral_constant<bool, HasUsedMemoryMethod<TMap>::Has>());
}

14
wtf là cái này ??? Là mã c ++ hợp pháp ?? bạn có thể viết "mẫu <tên chữ U, size_t (U :: *) () const>" ?? nhưng ... đó là một giải pháp tuyệt vời và mới! Tôi cảm ơn bạn, tôi sẽ phân tích tốt hơn vào ngày mai với đồng nghiệp của tôi ... tuyệt vời!
ugasoft

2
Ví dụ thiếu định nghĩa của 'int_to_type'. Rõ ràng là nó không thêm vào câu trả lời, nhưng điều đó có nghĩa là mọi người có thể thấy mã của bạn hoạt động sau khi cắt và dán nhanh.
Richard Corden

2
Một định nghĩa đơn giản về int_to_type có thể là: 'template <int N> struct int_to_type {};'. Nhiều triển khai giữ giá trị N của tham số trong enum hoặc khác trong hằng số nguyên tĩnh (template <int N> struct int_to_type {enum {value = N};}; / template <int N> struct int_to_type {static const int value = N;})
David Rodríguez - dribeas

2
Đơn giản chỉ cần tăng boost :: integ_constant thay vì int_to_type.
Vadim Ferderer

2
@JohanLundberg Đó là một hàm thành viên con trỏ đến (không tĩnh-). Ví dụ , size_t(std::vector::*p)() = &std::vector::size;.
Phục hồi lại

132

Đây là một triển khai có thể dựa trên các tính năng của C ++ 11. Nó phát hiện chính xác chức năng ngay cả khi nó được kế thừa (không giống như giải pháp trong câu trả lời được chấp nhận, như Mike Kinghan quan sát trong câu trả lời của mình ).

Hàm kiểm tra đoạn mã này được gọi là serialize:

#include <type_traits>

// Primary template with a static assertion
// for a meaningful error message
// if it ever gets instantiated.
// We could leave it undefined if we didn't care.

template<typename, typename T>
struct has_serialize {
    static_assert(
        std::integral_constant<T, false>::value,
        "Second template parameter needs to be of function type.");
};

// specialization that does the checking

template<typename C, typename Ret, typename... Args>
struct has_serialize<C, Ret(Args...)> {
private:
    template<typename T>
    static constexpr auto check(T*)
    -> typename
        std::is_same<
            decltype( std::declval<T>().serialize( std::declval<Args>()... ) ),
            Ret    // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        >::type;  // attempt to call it and see if the return type is correct

    template<typename>
    static constexpr std::false_type check(...);

    typedef decltype(check<C>(0)) type;

public:
    static constexpr bool value = type::value;
};

Sử dụng:

struct X {
     int serialize(const std::string&) { return 42; } 
};

struct Y : X {};

std::cout << has_serialize<Y, int(const std::string&)>::value; // will print 1

Điều này có hoạt động không nếu Y không có phương thức gọi là "tuần tự hóa"? Tôi không thấy nó sẽ trả về giá trị sai như thế nào nếu phương thức "tuần tự hóa" không tồn tại.
Collin

1
@Collin trong trường hợp đó thay thế tham số mẫu không thành công cho lần kiểm tra đầu tiên và nó bị loại bỏ khỏi tập quá tải. Nó rơi trở lại cái thứ hai trả về false_type. Đây không phải là lỗi trình biên dịch vì nguyên tắc SFINAE.
jrok

1
@ elios264 Không có. Bạn có thể sử dụng macro để viết mẫu cho từng chức năng bạn muốn kiểm tra.
jrok

1
Bất kỳ lý do cụ thể tại sao đối số để kiểm tra là loại T * chứ không phải T hoặc T &?
shibumi

1
Nhưng nếu serializebản thân nó chấp nhận một mẫu. Có cách nào để kiểm tra serializesự tồn tại mà không cần gõ chính xác không?
Hi-Angel

37

Câu trả lời được chấp nhận cho câu hỏi này về sự hướng nội của chức năng thành viên, mặc dù nó rất phổ biến, có một nhược điểm có thể được quan sát trong chương trình sau:

#include <type_traits>
#include <iostream>
#include <memory>

/*  Here we apply the accepted answer's technique to probe for the
    the existence of `E T::operator*() const`
*/
template<typename T, typename E>
struct has_const_reference_op
{
    template<typename U, E (U::*)() const> struct SFINAE {};
    template<typename U> static char Test(SFINAE<U, &U::operator*>*);
    template<typename U> static int Test(...);
    static const bool value = sizeof(Test<T>(0)) == sizeof(char);
};

using namespace std;

/* Here we test the `std::` smart pointer templates, including the
    deprecated `auto_ptr<T>`, to determine in each case whether
    T = (the template instantiated for `int`) provides 
    `int & T::operator*() const` - which all of them in fact do.
*/ 
int main(void)
{
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value << endl;
    return 0;
}

Được xây dựng với GCC 4.6.3, nghiệm thu sản phẩm 110- thông báo cho chúng ta biết rằng T = std::shared_ptr<int>không không cung cấp int & T::operator*() const.

Nếu bạn chưa khôn ngoan với gotcha này, thì hãy xem định nghĩa của std::shared_ptr<T>tiêu đề <memory>sẽ làm sáng tỏ. Trong triển khai đó, std::shared_ptr<T>được bắt nguồn từ một lớp cơ sở mà nó kế thừa operator*() const. Vì vậy, việc khởi tạo mẫu tạo SFINAE<U, &U::operator*>thành "tìm kiếm" toán tử U = std::shared_ptr<T>sẽ không xảy ra, bởi vì std::shared_ptr<T>không có operator*()quyền riêng của nó và khởi tạo mẫu không "thực hiện kế thừa".

Snag này không ảnh hưởng đến phương pháp SFINAE nổi tiếng, sử dụng "Thủ thuật sizeof ()", để phát hiện chỉ đơn thuần Tcó một số chức năng thành viên mf(xem ví dụ câu trả lời và nhận xét này). Nhưng thiết lập T::mftồn tại thường là (thường?) Không đủ tốt: bạn cũng có thể cần phải thiết lập rằng nó có chữ ký mong muốn. Đó là nơi điểm số kỹ thuật minh họa. Biến thể con trỏ của chữ ký mong muốn được ghi trong một tham số của loại mẫu phải được thỏa mãn bằng cách &T::mfthăm dò SFINAE để thành công. Nhưng kỹ thuật khởi tạo mẫu này đưa ra câu trả lời sai khi T::mfđược kế thừa.

Một kỹ thuật SFINAE an toàn cho việc xem xét nội bộ T::mfphải tránh sử dụng &T::mftrong một đối số khuôn mẫu để khởi tạo một kiểu mà độ phân giải mẫu hàm SFINAE phụ thuộc vào. Thay vào đó, độ phân giải chức năng mẫu SFINAE chỉ có thể phụ thuộc vào các khai báo loại thích hợp chính xác được sử dụng làm loại đối số của hàm thăm dò SFINAE bị quá tải.

Bằng cách trả lời cho câu hỏi tuân theo ràng buộc này, tôi sẽ minh họa cho việc phát hiện đồng thời E T::operator*() const, tùy ý TE. Mô hình tương tự sẽ áp dụng mutatis mutandis để thăm dò cho bất kỳ chữ ký phương thức thành viên nào khác.

#include <type_traits>

/*! The template `has_const_reference_op<T,E>` exports a
    boolean constant `value that is true iff `T` provides
    `E T::operator*() const`
*/ 
template< typename T, typename E>
struct has_const_reference_op
{
    /* SFINAE operator-has-correct-sig :) */
    template<typename A>
    static std::true_type test(E (A::*)() const) {
        return std::true_type();
    }

    /* SFINAE operator-exists :) */
    template <typename A> 
    static decltype(test(&A::operator*)) 
    test(decltype(&A::operator*),void *) {
        /* Operator exists. What about sig? */
        typedef decltype(test(&A::operator*)) return_type; 
        return return_type();
    }

    /* SFINAE game over :( */
    template<typename A>
    static std::false_type test(...) {
        return std::false_type(); 
    }

    /* This will be either `std::true_type` or `std::false_type` */
    typedef decltype(test<T>(0,0)) type;

    static const bool value = type::value; /* Which is it? */
};

Trong giải pháp này, chức năng thăm dò SFINAE bị quá tải test()được "gọi đệ quy". (Tất nhiên nó hoàn toàn không được gọi; nó chỉ có các kiểu trả về giả thuyết được giải quyết bởi trình biên dịch.)

Chúng ta cần thăm dò ít nhất một và nhiều nhất hai điểm thông tin:

  • T::operator*()tồn tại không? Nếu không, chúng ta đã xong.
  • Cho rằng T::operator*()tồn tại, là chữ ký của nó E T::operator*() const?

Chúng tôi nhận được câu trả lời bằng cách đánh giá loại trả về của một cuộc gọi đến test(0,0). Điều đó được thực hiện bởi:

    typedef decltype(test<T>(0,0)) type;

Cuộc gọi này có thể được giải quyết khi /* SFINAE operator-exists :) */quá tải test()hoặc có thể giải quyết /* SFINAE game over :( */tình trạng quá tải. Nó không thể giải quyết /* SFINAE operator-has-correct-sig :) */tình trạng quá tải, bởi vì người ta chỉ mong đợi một đối số và chúng ta sẽ vượt qua hai đối số.

Tại sao chúng ta đi qua hai? Đơn giản chỉ để buộc nghị quyết loại trừ /* SFINAE operator-has-correct-sig :) */. Đối số thứ hai không có ý nghĩa khác.

Lệnh gọi test(0,0)này sẽ giải quyết /* SFINAE operator-exists :) */chỉ trong trường hợp đối số đầu tiên 0 làm bão hòa loại tham số đầu tiên của tình trạng quá tải đó, đó là decltype(&A::operator*), với A = T. 0 sẽ đáp ứng loại đó chỉ trong trường hợp T::operator*tồn tại.

Giả sử trình biên dịch nói "Có với điều đó. Sau đó, nó sẽ đi /* SFINAE operator-exists :) */và nó cần xác định kiểu trả về của lệnh gọi hàm, trong trường hợp đó là decltype(test(&A::operator*))- kiểu trả về của một cuộc gọi khác test().

Lần này, chúng ta sẽ vượt qua chỉ một đối số &A::operator*, mà bây giờ chúng ta biết là tồn tại, hoặc chúng ta sẽ không ở đây. Một cuộc gọi đến test(&A::operator*)có thể giải quyết một /* SFINAE operator-has-correct-sig :) */hoặc một lần nữa để có thể giải quyết /* SFINAE game over :( */. Cuộc gọi sẽ khớp /* SFINAE operator-has-correct-sig :) */chỉ trong trường hợp &A::operator*thỏa mãn loại tham số duy nhất của tình trạng quá tải đó, đó là E (A::*)() const, với A = T.

Trình biên dịch sẽ nói Có ở đây nếu T::operator*có chữ ký mong muốn đó, và sau đó lại phải đánh giá kiểu trả về của quá tải. Không còn "thu hồi" bây giờ: nó là std::true_type.

Nếu trình biên dịch không chọn /* SFINAE operator-exists :) */cho cuộc gọi test(0,0)hoặc không chọn /* SFINAE operator-has-correct-sig :) */ cho cuộc gọi test(&A::operator*), thì trong cả hai trường hợp nó sẽ đi cùng /* SFINAE game over :( */và kiểu trả về cuối cùng là std::false_type.

Dưới đây là một chương trình thử nghiệm cho thấy mẫu tạo ra các câu trả lời dự kiến ​​trong các trường hợp mẫu khác nhau (GCC 4.6.3 một lần nữa).

// To test
struct empty{};

// To test 
struct int_ref
{
    int & operator*() const {
        return *_pint;
    }
    int & foo() const {
        return *_pint;
    }
    int * _pint;
};

// To test 
struct sub_int_ref : int_ref{};

// To test 
template<typename E>
struct ee_ref
{
    E & operator*() {
        return *_pe;
    }
    E & foo() const {
        return *_pe;
    }
    E * _pe;
};

// To test 
struct sub_ee_ref : ee_ref<char>{};

using namespace std;

#include <iostream>
#include <memory>
#include <vector>

int main(void)
{
    cout << "Expect Yes" << endl;
    cout << has_const_reference_op<auto_ptr<int>,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int &>::value;
    cout << has_const_reference_op<shared_ptr<int>,int &>::value;
    cout << has_const_reference_op<std::vector<int>::iterator,int &>::value;
    cout << has_const_reference_op<std::vector<int>::const_iterator,
            int const &>::value;
    cout << has_const_reference_op<int_ref,int &>::value;
    cout << has_const_reference_op<sub_int_ref,int &>::value  << endl;
    cout << "Expect No" << endl;
    cout << has_const_reference_op<int *,int &>::value;
    cout << has_const_reference_op<unique_ptr<int>,char &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int const &>::value;
    cout << has_const_reference_op<unique_ptr<int>,int>::value;
    cout << has_const_reference_op<unique_ptr<long>,int &>::value;
    cout << has_const_reference_op<int,int>::value;
    cout << has_const_reference_op<std::vector<int>,int &>::value;
    cout << has_const_reference_op<ee_ref<int>,int &>::value;
    cout << has_const_reference_op<sub_ee_ref,int &>::value;
    cout << has_const_reference_op<empty,int &>::value  << endl;
    return 0;
}

Có những sai sót mới trong ý tưởng này? Nó có thể được làm cho chung chung hơn mà không một lần nữa phạm lỗi của snag nó tránh?


16

Dưới đây là một số đoạn sử dụng: * Sự can đảm cho tất cả những điều này ở xa hơn

Kiểm tra thành viên xtrong một lớp nhất định. Có thể là var, func, class, union hoặc enum:

CREATE_MEMBER_CHECK(x);
bool has_x = has_member_x<class_to_check_for_x>::value;

Kiểm tra chức năng thành viên void x():

//Func signature MUST have T as template variable here... simpler this way :\
CREATE_MEMBER_FUNC_SIG_CHECK(x, void (T::*)(), void__x);
bool has_func_sig_void__x = has_member_func_void__x<class_to_check_for_x>::value;

Kiểm tra biến thành viên x:

CREATE_MEMBER_VAR_CHECK(x);
bool has_var_x = has_member_var_x<class_to_check_for_x>::value;

Kiểm tra lớp thành viên x:

CREATE_MEMBER_CLASS_CHECK(x);
bool has_class_x = has_member_class_x<class_to_check_for_x>::value;

Kiểm tra thành viên công đoàn x:

CREATE_MEMBER_UNION_CHECK(x);
bool has_union_x = has_member_union_x<class_to_check_for_x>::value;

Kiểm tra enum thành viên x:

CREATE_MEMBER_ENUM_CHECK(x);
bool has_enum_x = has_member_enum_x<class_to_check_for_x>::value;

Kiểm tra bất kỳ chức năng thành viên xbất kể chữ ký:

CREATE_MEMBER_CHECK(x);
CREATE_MEMBER_VAR_CHECK(x);
CREATE_MEMBER_CLASS_CHECK(x);
CREATE_MEMBER_UNION_CHECK(x);
CREATE_MEMBER_ENUM_CHECK(x);
CREATE_MEMBER_FUNC_CHECK(x);
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

HOẶC LÀ

CREATE_MEMBER_CHECKS(x);  //Just stamps out the same macro calls as above.
bool has_any_func_x = has_member_func_x<class_to_check_for_x>::value;

Chi tiết và cốt lõi:

/*
    - Multiple inheritance forces ambiguity of member names.
    - SFINAE is used to make aliases to member names.
    - Expression SFINAE is used in just one generic has_member that can accept
      any alias we pass it.
*/

//Variadic to force ambiguity of class members.  C++11 and up.
template <typename... Args> struct ambiguate : public Args... {};

//Non-variadic version of the line above.
//template <typename A, typename B> struct ambiguate : public A, public B {};

template<typename A, typename = void>
struct got_type : std::false_type {};

template<typename A>
struct got_type<A> : std::true_type {
    typedef A type;
};

template<typename T, T>
struct sig_check : std::true_type {};

template<typename Alias, typename AmbiguitySeed>
struct has_member {
    template<typename C> static char ((&f(decltype(&C::value))))[1];
    template<typename C> static char ((&f(...)))[2];

    //Make sure the member name is consistently spelled the same.
    static_assert(
        (sizeof(f<AmbiguitySeed>(0)) == 1)
        , "Member name specified in AmbiguitySeed is different from member name specified in Alias, or wrong Alias/AmbiguitySeed has been specified."
    );

    static bool const value = sizeof(f<Alias>(0)) == 2;
};

Macros (El Diablo!):

CREATE_MEMBER_CHECK:

//Check for any member with given name, whether var, func, class, union, enum.
#define CREATE_MEMBER_CHECK(member)                                         \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct Alias_##member;                                                      \
                                                                            \
template<typename T>                                                        \
struct Alias_##member <                                                     \
    T, std::integral_constant<bool, got_type<decltype(&T::member)>::value>  \
> { static const decltype(&T::member) value; };                             \
                                                                            \
struct AmbiguitySeed_##member { char member; };                             \
                                                                            \
template<typename T>                                                        \
struct has_member_##member {                                                \
    static const bool value                                                 \
        = has_member<                                                       \
            Alias_##member<ambiguate<T, AmbiguitySeed_##member>>            \
            , Alias_##member<AmbiguitySeed_##member>                        \
        >::value                                                            \
    ;                                                                       \
}

CREATE_MEMBER_VAR_CHECK:

//Check for member variable with given name.
#define CREATE_MEMBER_VAR_CHECK(var_name)                                   \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_var_##var_name : std::false_type {};                      \
                                                                            \
template<typename T>                                                        \
struct has_member_var_##var_name<                                           \
    T                                                                       \
    , std::integral_constant<                                               \
        bool                                                                \
        , !std::is_member_function_pointer<decltype(&T::var_name)>::value   \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_SIG_CHECK:

//Check for member function with given name AND signature.
#define CREATE_MEMBER_FUNC_SIG_CHECK(func_name, func_sig, templ_postfix)    \
                                                                            \
template<typename T, typename = std::true_type>                             \
struct has_member_func_##templ_postfix : std::false_type {};                \
                                                                            \
template<typename T>                                                        \
struct has_member_func_##templ_postfix<                                     \
    T, std::integral_constant<                                              \
        bool                                                                \
        , sig_check<func_sig, &T::func_name>::value                         \
    >                                                                       \
> : std::true_type {}

CREATE_MEMBER_CLASS_CHECK:

//Check for member class with given name.
#define CREATE_MEMBER_CLASS_CHECK(class_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_class_##class_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_class_##class_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_class<                                    \
            typename got_type<typename T::class_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_UNION_CHECK:

//Check for member union with given name.
#define CREATE_MEMBER_UNION_CHECK(union_name)               \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_union_##union_name : std::false_type {};  \
                                                            \
template<typename T>                                        \
struct has_member_union_##union_name<                       \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_union<                                    \
            typename got_type<typename T::union_name>::type \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_ENUM_CHECK:

//Check for member enum with given name.
#define CREATE_MEMBER_ENUM_CHECK(enum_name)                 \
                                                            \
template<typename T, typename = std::true_type>             \
struct has_member_enum_##enum_name : std::false_type {};    \
                                                            \
template<typename T>                                        \
struct has_member_enum_##enum_name<                         \
    T                                                       \
    , std::integral_constant<                               \
        bool                                                \
        , std::is_enum<                                     \
            typename got_type<typename T::enum_name>::type  \
        >::value                                            \
    >                                                       \
> : std::true_type {}

CREATE_MEMBER_FUNC_CHECK:

//Check for function with given name, any signature.
#define CREATE_MEMBER_FUNC_CHECK(func)          \
template<typename T>                            \
struct has_member_func_##func {                 \
    static const bool value                     \
        = has_member_##func<T>::value           \
        && !has_member_var_##func<T>::value     \
        && !has_member_class_##func<T>::value   \
        && !has_member_union_##func<T>::value   \
        && !has_member_enum_##func<T>::value    \
    ;                                           \
}

CREATE_MEMBER_CHECKS:

//Create all the checks for one member.  Does NOT include func sig checks.
#define CREATE_MEMBER_CHECKS(member)    \
CREATE_MEMBER_CHECK(member);            \
CREATE_MEMBER_VAR_CHECK(member);        \
CREATE_MEMBER_CLASS_CHECK(member);      \
CREATE_MEMBER_UNION_CHECK(member);      \
CREATE_MEMBER_ENUM_CHECK(member);       \
CREATE_MEMBER_FUNC_CHECK(member)

1
Điều đó thật tuyệt; sẽ rất tốt nếu đặt nó trong một thư viện tệp tiêu đề duy nhất.
Allan

12

Điều này là đủ, nếu bạn biết tên của chức năng thành viên bạn đang mong đợi. (Trong trường hợp này, hàm bla không thể khởi tạo nếu không có hàm thành viên (việc viết một hàm dù sao cũng khó khăn vì thiếu chuyên môn hóa một phần chức năng. Bạn có thể cần sử dụng các mẫu lớp) Ngoài ra, hàm struct (mà tương tự như enable_if) cũng có thể được đặt trên loại hàm mà bạn muốn nó có với tư cách là thành viên.

template <typename T, int (T::*) ()> struct enable { typedef T type; };
template <typename T> typename enable<T, &T::i>::type bla (T&);
struct A { void i(); };
struct B { int i(); };
int main()
{
  A a;
  B b;
  bla(b);
  bla(a);
}

4
thaks! nó tương tự như giải pháp được đề xuất bởi yrp. Tôi không biết rằng mẫu có thể được tạo mẫu trên các chức năng thành viên. Đó là một tính năng mới mà tôi đã học ngày hôm nay! ... và một bài học mới: "đừng bao giờ nói bạn là chuyên gia về c ++" :)
ugasoft

7

Đây là một cách đơn giản hơn về câu trả lời của Mike Kinghan. Điều này sẽ phát hiện các phương thức kế thừa. Nó cũng sẽ kiểm tra chữ ký chính xác (không giống như cách tiếp cận của jrok cho phép chuyển đổi đối số).

template <class C>
class HasGreetMethod
{
    template <class T>
    static std::true_type testSignature(void (T::*)(const char*) const);

    template <class T>
    static decltype(testSignature(&T::greet)) test(std::nullptr_t);

    template <class T>
    static std::false_type test(...);

public:
    using type = decltype(test<C>(nullptr));
    static const bool value = type::value;
};

struct A { void greet(const char* name) const; };
struct Derived : A { };
static_assert(HasGreetMethod<Derived>::value, "");

Ví dụ runnable


Điều này là tốt, nhưng nó sẽ không hoạt động nếu chức năng không có đối số
Triskeldeian

Nó làm việc rất tốt. Tôi không có bất kỳ vấn đề nào khi áp dụng thủ thuật này cho các chức năng thành viên không có đối số.
JohnB

Điều này hoạt động tốt với tôi với nhiều và không có đối số phương thức, bao gồm cả quá tải và bao gồm cả kế thừa và với việc sử dụng usingđể mang lại quá tải từ lớp cơ sở. Nó hoạt động với tôi trên MSVC 2015 và với Clang-CL. Nó không hoạt động với MSVC 2012 tuy nhiên.
steveire

5

Bạn có thể sử dụng std :: is_member_feft_pulum

class A {
   public:
     void foo() {};
}

 bool test = std::is_member_function_pointer<decltype(&A::foo)>::value;

16
Sẽ không phải &A::foolà một lỗi biên dịch nếu không có gì foocả A? Tôi đọc câu hỏi ban đầu như được cho là làm việc với bất kỳ lớp đầu vào nào, không chỉ những câu hỏi có một số loại thành viên được đặt tên foo.
Jeff Walden

5

Đã đến với cùng một loại vấn đề, và thấy các giải pháp được đề xuất ở đây rất thú vị ... nhưng có yêu cầu cho một giải pháp:

  1. Phát hiện các chức năng được kế thừa là tốt;
  2. Tương thích với các trình biên dịch sẵn sàng không C ++ 11 (vì vậy không có dectype)

Tìm thấy một chủ đề khác đề xuất một cái gì đó như thế này, dựa trên một cuộc thảo luận BOOST . Dưới đây là khái quát hóa của giải pháp được đề xuất dưới dạng hai khai báo macro cho lớp đặc điểm, theo mô hình của các lớp boost :: has_ ​​* .

#include <boost/type_traits/is_class.hpp>
#include <boost/mpl/vector.hpp>

/// Has constant function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC_C(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(1, func_ret_type, func_name, ##__VA_ARGS__)

/// Has non-const function
/** \param func_ret_type Function return type
    \param func_name Function name
    \param ... Variadic arguments are for the function parameters
*/
#define DECLARE_TRAITS_HAS_FUNC(func_ret_type, func_name, ...) \
    __DECLARE_TRAITS_HAS_FUNC(0, func_ret_type, func_name, ##__VA_ARGS__)

// Traits content
#define __DECLARE_TRAITS_HAS_FUNC(func_const, func_ret_type, func_name, ...)  \
    template                                                                  \
    <   typename Type,                                                        \
        bool is_class = boost::is_class<Type>::value                          \
    >                                                                         \
    class has_func_ ## func_name;                                             \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,false>                                  \
    {public:                                                                  \
        BOOST_STATIC_CONSTANT( bool, value = false );                         \
        typedef boost::false_type type;                                       \
    };                                                                        \
    template<typename Type>                                                   \
    class has_func_ ## func_name<Type,true>                                   \
    {   struct yes { char _foo; };                                            \
        struct no { yes _foo[2]; };                                           \
        struct Fallback                                                       \
        {   func_ret_type func_name( __VA_ARGS__ )                            \
                UTILITY_OPTIONAL(func_const,const) {}                         \
        };                                                                    \
        struct Derived : public Type, public Fallback {};                     \
        template <typename T, T t>  class Helper{};                           \
        template <typename U>                                                 \
        static no deduce(U*, Helper                                           \
            <   func_ret_type (Fallback::*)( __VA_ARGS__ )                    \
                    UTILITY_OPTIONAL(func_const,const),                       \
                &U::func_name                                                 \
            >* = 0                                                            \
        );                                                                    \
        static yes deduce(...);                                               \
    public:                                                                   \
        BOOST_STATIC_CONSTANT(                                                \
            bool,                                                             \
            value = sizeof(yes)                                               \
                == sizeof( deduce( static_cast<Derived*>(0) ) )               \
        );                                                                    \
        typedef ::boost::integral_constant<bool,value> type;                  \
        BOOST_STATIC_CONSTANT(bool, is_const = func_const);                   \
        typedef func_ret_type return_type;                                    \
        typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;                \
    }

// Utility functions
#define UTILITY_OPTIONAL(condition, ...) UTILITY_INDIRECT_CALL( __UTILITY_OPTIONAL_ ## condition , ##__VA_ARGS__ )
#define UTILITY_INDIRECT_CALL(macro, ...) macro ( __VA_ARGS__ )
#define __UTILITY_OPTIONAL_0(...)
#define __UTILITY_OPTIONAL_1(...) __VA_ARGS__

Các macro này mở rộng thành một lớp đặc điểm với nguyên mẫu sau:

template<class T>
class has_func_[func_name]
{
public:
    /// Function definition result value
    /** Tells if the tested function is defined for type T or not.
    */
    static const bool value = true | false;

    /// Function definition result type
    /** Type representing the value attribute usable in
        http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html
    */
    typedef boost::integral_constant<bool,value> type;

    /// Tested function constness indicator
    /** Indicates if the tested function is const or not.
        This value is not deduced, it is forced depending
        on the user call to one of the traits generators.
    */
    static const bool is_const = true | false;

    /// Tested function return type
    /** Indicates the return type of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef func_ret_type return_type;

    /// Tested function arguments types
    /** Indicates the arguments types of the tested function.
        This value is not deduced, it is forced depending
        on the user's arguments to the traits generators.
    */
    typedef ::boost::mpl::vector< __VA_ARGS__ > args_type;
};

Vì vậy, cách sử dụng điển hình người ta có thể làm gì trong số này?

// We enclose the traits class into
// a namespace to avoid collisions
namespace ns_0 {
    // Next line will declare the traits class
    // to detect the member function void foo(int,int) const
    DECLARE_TRAITS_HAS_FUNC_C(void, foo, int, int);
}

// we can use BOOST to help in using the traits
#include <boost/utility/enable_if.hpp>

// Here is a function that is active for types
// declaring the good member function
template<typename T> inline
typename boost::enable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   _this_.foo(a,b);
}

// Here is a function that is active for types
// NOT declaring the good member function
template<typename T> inline
typename boost::disable_if< ns_0::has_func_foo<T> >::type
foo_bar(const T &_this_, int a=0, int b=1)
{   default_foo(_this_,a,b);
}

// Let us declare test types
struct empty
{
};
struct direct_foo
{
    void foo(int,int);
};
struct direct_const_foo
{
    void foo(int,int) const;
};
struct inherited_const_foo :
    public direct_const_foo
{
};

// Now anywhere in your code you can seamlessly use
// the foo_bar function on any object:
void test()
{
    int a;
    foo_bar(a); // calls default_foo

    empty b;
    foo_bar(b); // calls default_foo

    direct_foo c;
    foo_bar(c); // calls default_foo (member function is not const)

    direct_const_foo d;
    foo_bar(d); // calls d.foo (member function is const)

    inherited_const_foo e;
    foo_bar(e); // calls e.foo (inherited member function)
}

5

Để thực hiện điều này, chúng tôi sẽ cần sử dụng:

  1. Mẫu chức năng nạp chồng với các kiểu trả về khác nhau tùy theo phương thức có sẵn không
  2. Để phù hợp với các điều kiện meta trong type_traitstiêu đề, chúng tôi sẽ muốn trả lại một true_typehoặcfalse_type từ tình trạng quá tải của chúng tôi
  3. Khai báo true_typetình trạng quá tải mong đợi intfalse_typequá tải mong đợi Thông số Variadic khai thác: "Ưu tiên thấp nhất của chuyển đổi dấu chấm lửng trong độ phân giải quá tải"
  4. Trong việc xác định đặc tả mẫu cho true_type hàm, chúng tôi sẽ sử dụng declvaldecltypecho phép chúng tôi phát hiện hàm độc lập với sự khác biệt hoặc quá tải kiểu trả về giữa các phương thức

Bạn có thể xem một ví dụ trực tiếp về điều này ở đây . Nhưng tôi cũng sẽ giải thích nó dưới đây:

Tôi muốn kiểm tra sự tồn tại của một hàm có tên test là loại chuyển đổi từ intđó, sau đó tôi cần khai báo hai hàm này:

template <typename T, typename S = decltype(declval<T>().test(declval<int>))> static true_type hasTest(int);
template <typename T> static false_type hasTest(...);
  • decltype(hasTest<a>(0))::valuetrue (Lưu ý không cần tạo chức năng đặc biệt để đối phó với void a::test()tình trạng quá tải, void a::test(int)được chấp nhận)
  • decltype(hasTest<b>(0))::valuetrue (Vì intcó thể chuyển đổi thành double int b::test(double)được chấp nhận, không phụ thuộc vào loại trả về)
  • decltype(hasTest<c>(0))::valuefalse ( ckhông có một phương thức có tên testchấp nhận một loại chuyển đổi từ intđó vì điều này không được chấp nhận)

Giải pháp này có 2 nhược điểm:

  1. Yêu cầu khai báo theo phương thức của một cặp hàm
  2. Tạo ô nhiễm không gian tên đặc biệt nếu chúng ta muốn kiểm tra các tên tương tự, ví dụ: chúng ta sẽ đặt tên cho hàm là gì để kiểm tra test() phương thức?

Vì vậy, điều quan trọng là các hàm này được khai báo trong một không gian tên chi tiết hoặc lý tưởng là nếu chúng chỉ được sử dụng với một lớp, chúng nên được khai báo riêng bởi lớp đó. Vì vậy, tôi đã viết một macro để giúp bạn trừu tượng hóa thông tin này:

#define FOO(FUNCTION, DEFINE) template <typename T, typename S = decltype(declval<T>().FUNCTION)> static true_type __ ## DEFINE(int); \
                              template <typename T> static false_type __ ## DEFINE(...); \
                              template <typename T> using DEFINE = decltype(__ ## DEFINE<T>(0));

Bạn có thể sử dụng như thế này:

namespace details {
    FOO(test(declval<int>()), test_int)
    FOO(test(), test_void)
}

Sau đó gọi details::test_int<a>::valuehoặc details::test_void<a>::valuesẽ mang lại truehoặc falsecho các mục đích của mã nội tuyến hoặc lập trình meta.


3

Để không xâm phạm, bạn cũng có thể đặt serializevào không gian tên của lớp đang được tuần tự hóa hoặc của lớp lưu trữ, nhờ tra cứu Koenig . Xem Không gian tên cho Ghi đè chức năng miễn phí để biết thêm chi tiết. :-)

Mở ra bất kỳ không gian tên cụ thể nào để thực hiện một chức năng miễn phí là Simply Wrong. (ví dụ: bạn không cần phải mở không gian tên stdđể triển khai swapcho các loại của riêng mình, nhưng nên sử dụng tra cứu Koenig thay thế.)


3

Bạn có vẻ muốn thành ngữ dò tìm. Các câu trả lời ở trên là các biến thể về điều này hoạt động với C ++ 11 hoặc C ++ 14.

Các std::experimentalthư viện có nhiều tính năng mà làm chủ yếu này. Làm lại một ví dụ từ trên, nó có thể là:

#include <experimental/type_traits>

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template<typename T>
using has_serialize_t = std::experimental::is_detected_t<serialized_method_t, T>;

Nếu bạn không thể sử dụng std :: thử nghiệm, một phiên bản thô sơ có thể được tạo như thế này:

template <typename... Ts>
using void_t = void;
template <template <class...> class Trait, class AlwaysVoid, class... Args>
struct detector : std::false_type {};
template <template <class...> class Trait, class... Args>
struct detector<Trait, void_t<Trait<Args...>>, Args...> : std::true_type {};

// serialized_method_t is a detector type for T.serialize(int) const
template<typename T>
using serialized_method_t = decltype(std::declval<const T&>.serialize(std::declval<int>()));

// has_serialize_t is std::true_type when T.serialize(int) exists,
// and false otherwise.
template <typename T>
using has_serialize_t = typename detector<serialized_method_t, void, T>::type;

Vì has_serialize_t thực sự là std :: true_type hoặc std :: false_type, nên nó có thể được sử dụng thông qua bất kỳ thành ngữ SFINAE phổ biến nào:

template<class T>
std::enable_if_t<has_serialize_t<T>::value, std::string>
SerializeToString(const T& t) {
}

Hoặc bằng cách sử dụng công văn với độ phân giải quá tải:

template<class T>
std::string SerializeImpl(std::true_type, const T& t) {
  // call serialize here.
}

template<class T>
std::string SerializeImpl(std::false_type, const T& t) {
  // do something else here.
}

template<class T>
std::string Serialize(const T& t) {
  return SerializeImpl(has_serialize_t<T>{}, t);
}

2

Được chứ. Thử lần thứ hai. Không sao nếu bạn không thích cái này, tôi đang tìm thêm ý tưởng.

Bài báo của Herb Sutter nói về những đặc điểm. Vì vậy, bạn có thể có một lớp đặc điểm mà việc khởi tạo mặc định của nó có hành vi dự phòng và đối với mỗi lớp có chức năng thành viên của bạn, thì lớp đặc điểm đó là chuyên biệt để gọi hàm thành viên. Tôi tin rằng bài viết của Herb đề cập đến một kỹ thuật để làm điều này để nó không liên quan đến việc sao chép và dán.

Tuy nhiên, như tôi đã nói, có lẽ bạn không muốn có thêm công việc liên quan đến các lớp "gắn thẻ" thực hiện thành viên đó. Trong trường hợp đó, tôi đang xem xét một giải pháp thứ ba ....


eh ... tôi đã phân tích giải pháp này ... Tôi nghĩ rằng nó hơi quá đắt đối với người dùng trong khuôn khổ của tôi. (ok, tôi thừa nhận, tôi đang phát triển một khung phát trực tuyến và tôi đang chọn giữa việc mở rộng iostream hoặc viết lại một cái gì đó dễ dàng hơn)
ugasoft

Giải pháp thứ ba của tôi là sử dụng SFINAE. Vì câu trả lời của bạn đã đề cập đến nó, tôi sẽ không đi sâu vào nó (vì tôi vẫn đang nghiên cứu về nó: Tôi biết ý tưởng, nhưng ma quỷ nằm trong chi tiết), trừ khi cuối cùng giải pháp của anh ấy không hiệu quả với bạn . :-)
Chris Jester-Young

1

Không có hỗ trợ C ++ 11 ( decltype), điều này có thể hoạt động:

SSCCE

#include <iostream>
using namespace std;

struct A { void foo(void); };
struct Aa: public A { };
struct B { };

struct retA { int foo(void); };
struct argA { void foo(double); };
struct constA { void foo(void) const; };
struct varA { int foo; };

template<typename T>
struct FooFinder {
    typedef char true_type[1];
    typedef char false_type[2];

    template<int>
    struct TypeSink;

    template<class U>
    static true_type &match(U);

    template<class U>
    static true_type &test(TypeSink<sizeof( matchType<void (U::*)(void)>( &U::foo ) )> *);

    template<class U>
    static false_type &test(...);

    enum { value = (sizeof(test<T>(0, 0)) == sizeof(true_type)) };
};

int main() {
    cout << FooFinder<A>::value << endl;
    cout << FooFinder<Aa>::value << endl;
    cout << FooFinder<B>::value << endl;

    cout << FooFinder<retA>::value << endl;
    cout << FooFinder<argA>::value << endl;
    cout << FooFinder<constA>::value << endl;
    cout << FooFinder<varA>::value << endl;
}

Làm thế nào nó hy vọng hoạt động

A, AaBlà clases trong câu hỏi,Aa là một trong những đặc biệt mà kế thừa các thành viên, chúng tôi đang tìm kiếm.

Trong FooFindercác true_typefalse_typelà sự thay thế cho các phóng viên C ++ 11 lớp học. Ngoài ra để hiểu về lập trình meta mẫu, họ tiết lộ chính cơ sở của thủ thuật SFINAE-sizeof-trick.

Đây TypeSinklà một cấu trúc mẫu được sử dụng sau này để đưa kết quả tách rời của sizeoftoán tử vào một khởi tạo mẫu để tạo thành một kiểu.

Các matchchức năng là một loại SFINAE của mẫu những gì còn lại mà không có một đối tác chung. Do đó, nó chỉ có thể được khởi tạo nếu loại đối số của nó khớp với loại mà nó được chuyên dùng.

Cả hai testchức năng cùng với khai báo enum cuối cùng tạo thành mẫu SFINAE trung tâm. Có một cái chung sử dụng dấu chấm lửng trả về false_typevà một đối tác với các đối số cụ thể hơn để được ưu tiên.

Để có thể khởi tạo testhàm bằng một đối số khuôn mẫu T, matchhàm phải được khởi tạo, vì kiểu trả về của nó là bắt buộc để khởi tạo TypeSinkđối số. Thông báo trước là &U::foo, được bao bọc trong một đối số chức năng, không được tham chiếu từ bên trong chuyên môn hóa đối số mẫu, do đó việc tra cứu thành viên được kế thừa vẫn diễn ra.


1

Nếu bạn đang sử dụng facebook folly, họ sẽ không sử dụng macro để giúp bạn:

#include <folly/Traits.h>
namespace {
  FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_test_traits, test);
} // unnamed-namespace

void some_func() {
  cout << "Does class Foo have a member int test() const? "
    << boolalpha << has_test_traits<Foo, int() const>::value;
}

Mặc dù các chi tiết thực hiện giống với câu trả lời trước đó, sử dụng thư viện đơn giản hơn.


0

Tôi có một nhu cầu tương tự và tình cờ gặp SO này. Có nhiều giải pháp thú vị / mạnh mẽ được đề xuất ở đây, mặc dù nó hơi dài cho một nhu cầu cụ thể: phát hiện nếu một lớp có chức năng thành viên với một chữ ký chính xác. Vì vậy, tôi đã đọc / kiểm tra và đưa ra phiên bản có thể được quan tâm. Nó phát hiện:

  • chức năng thành viên tĩnh
  • chức năng thành viên không tĩnh
  • Hàm thành viên không tĩnh const

với một chữ ký chính xác. Vì tôi không cần phải nắm bắt bất kỳ chữ ký nào (điều đó đòi hỏi một giải pháp phức tạp hơn), nên đây là một bộ cho tôi. Về cơ bản, nó được sử dụng enable_if_t .

struct Foo{ static int sum(int, const double&){return 0;} };
struct Bar{ int calc(int, const double&) {return 1;} };
struct BarConst{ int calc(int, const double&) const {return 1;} };

// Note : second typename can be void or anything, as long as it is consistent with the result of enable_if_t
template<typename T, typename = T> struct has_static_sum : std::false_type {};
template<typename T>
struct has_static_sum<typename T,
                        std::enable_if_t<std::is_same<decltype(T::sum), int(int, const double&)>::value,T> 
                      > : std::true_type {};

template<typename T, typename = T> struct has_calc : std::false_type {};
template<typename T>
struct has_calc <typename T,
                  std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&)>::value,T>
                > : std::true_type {};

template<typename T, typename = T> struct has_calc_const : std::false_type {};
template<typename T>
struct has_calc_const <typename T,
                        std::enable_if_t<std::is_same<decltype(&T::calc), int(T::*)(int, const double&) const>::value,T>
                      > : std::true_type {};

int main ()
{
    constexpr bool has_sum_val = has_static_sum<Foo>::value;
    constexpr bool not_has_sum_val = !has_static_sum<Bar>::value;

    constexpr bool has_calc_val = has_calc<Bar>::value;
    constexpr bool not_has_calc_val = !has_calc<Foo>::value;

    constexpr bool has_calc_const_val = has_calc_const<BarConst>::value;
    constexpr bool not_has_calc_const_val = !has_calc_const<Bar>::value;

    std::cout<< "           has_sum_val " << has_sum_val            << std::endl
             << "       not_has_sum_val " << not_has_sum_val        << std::endl
             << "          has_calc_val " << has_calc_val           << std::endl
             << "      not_has_calc_val " << not_has_calc_val       << std::endl
             << "    has_calc_const_val " << has_calc_const_val     << std::endl
             << "not_has_calc_const_val " << not_has_calc_const_val << std::endl;
}

Đầu ra:

           has_sum_val 1
       not_has_sum_val 1
          has_calc_val 1
      not_has_calc_val 1
    has_calc_const_val 1
not_has_calc_const_val 1

0

Dựa trên câu trả lời của jrok , tôi đã tránh sử dụng các lớp và / hoặc các hàm mẫu lồng nhau.

#include <type_traits>

#define CHECK_NESTED_FUNC(fName) \
    template <typename, typename, typename = std::void_t<>> \
    struct _has_##fName \
    : public std::false_type {}; \
    \
    template <typename Class, typename Ret, typename... Args> \
    struct _has_##fName<Class, Ret(Args...), \
        std::void_t<decltype(std::declval<Class>().fName(std::declval<Args>()...))>> \
    : public std::is_same<decltype(std::declval<Class>().fName(std::declval<Args>()...)), Ret> \
    {}; \
    \
    template <typename Class, typename Signature> \
    using has_##fName = _has_##fName<Class, Signature>;

#define HAS_NESTED_FUNC(Class, Func, Signature) has_##Func<Class, Signature>::value

Chúng ta có thể sử dụng các macro ở trên như dưới đây:

class Foo
{
public:
    void Bar(int, const char *) {}
};

CHECK_NESTED_FUNC(Bar);  // generate required metafunctions

int main()
{
    using namespace std;
    cout << boolalpha
         << HAS_NESTED_FUNC(Foo, Bar, void(int, const char *))  // prints true
         << endl;
    return 0;
}

Đề nghị được chào đón.

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.