Kiểm tra tạm thời cho sự tồn tại của một chức năng thành viên lớp?


498

Có thể viết một mẫu thay đổi hành vi tùy thuộc vào việc một hàm thành viên nhất định được xác định trên một lớp không?

Đây là một ví dụ đơn giản về những gì tôi muốn viết:

template<class T>
std::string optionalToString(T* obj)
{
    if (FUNCTION_EXISTS(T->toString))
        return obj->toString();
    else
        return "toString not defined";
}

Vì vậy, nếu class Tđã toString()xác định, thì nó sử dụng nó; mặt khác, nó không. Phần ma thuật mà tôi không biết làm thế nào là phần "FUNCTION_EXISTS".


6
Tất nhiên, không cần phải nói rằng (các) câu trả lời mẫu bên dưới chỉ hoạt động với thông tin thời gian biên dịch, tức là T phải có toString. Nếu bạn vượt qua trong một lớp con của T không xác định toString, nhưng T thì không , bạn sẽ được thông báo choString không được xác định.
Alice Purcell

Câu trả lời:


319

Có, với SFINAE, bạn có thể kiểm tra xem một lớp nhất định có cung cấp một phương thức nhất định không. Đây là mã làm việc:

#include <iostream>

struct Hello
{
    int helloworld() { return 0; }
};

struct Generic {};    

// SFINAE test
template <typename T>
class has_helloworld
{
    typedef char one;
    struct two { char x[2]; };

    template <typename C> static one test( typeof(&C::helloworld) ) ;
    template <typename C> static two test(...);    

public:
    enum { value = sizeof(test<T>(0)) == sizeof(char) };
};

int main(int argc, char *argv[])
{
    std::cout << has_helloworld<Hello>::value << std::endl;
    std::cout << has_helloworld<Generic>::value << std::endl;
    return 0;
}

Tôi vừa thử nghiệm nó với Linux và gcc 4.1 / 4.3. Tôi không biết nếu nó di động sang các nền tảng khác chạy các trình biên dịch khác nhau.


18
Mặc dù, tôi đã sử dụng cách sau cho 'một' và 'hai': typedef char Small; class Big {char dummy [2];} để đảm bảo không có sự mơ hồ về kích thước biến phụ thuộc nền tảng.
dùng23167

6
Tôi nghi ngờ nó tồn tại trên trái đất một nền tảng với sizeof (char) == sizeof (dài)
Nicola Bonelli

17
Tôi không hoàn toàn chắc chắn, nhưng tôi không nghĩ đây là hàng xách tay. typeof là một phần mở rộng GCC, điều này sẽ không hoạt động trên các trình biên dịch khác.
Leon Timmermans

56
typeof không cần thiết - char [sizeof (& C :: helloworld)] cũng hoạt động. Và để tránh sizeof (dài) == sizeof (char), hãy sử dụng struct {char [2]};. Nó phải có kích thước> = 2
MSalters

57
Không quan trọng, nhưng tôi đã mất một lúc để tìm ra: thay thế typeofbằng decltypekhi sử dụng C ++ 0x , ví dụ: qua -std = c ++ 0x.
hrr

264

Câu hỏi này đã cũ, nhưng với C ++ 11, chúng tôi đã có một cách mới để kiểm tra sự tồn tại của các chức năng (hoặc sự tồn tại của bất kỳ thành viên không thuộc loại nào, thực sự), dựa vào SFINAE một lần nữa:

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, int)
    -> decltype(os << obj, void())
{
  os << obj;
}

template<class T>
auto serialize_imp(std::ostream& os, T const& obj, long)
    -> decltype(obj.stream(os), void())
{
  obj.stream(os);
}

template<class T>
auto serialize(std::ostream& os, T const& obj)
    -> decltype(serialize_imp(os, obj, 0), void())
{
  serialize_imp(os, obj, 0);
}

Bây giờ vào một số giải thích. Điều đầu tiên, tôi sử dụng biểu thức SFINAE để loại trừ các serialize(_imp)hàm khỏi độ phân giải quá tải, nếu biểu thức đầu tiên bên trong decltypekhông hợp lệ (hay còn gọi là hàm không tồn tại).

Các void()được sử dụng để thực hiện các kiểu trả về của tất cả những chức năng void.

Đối 0số được sử dụng để ưu tiên os << objquá tải nếu cả hai đều có sẵn (nghĩa đen 0là loại intvà do đó, quá tải đầu tiên là phù hợp hơn).


Bây giờ, bạn có thể muốn một đặc điểm để kiểm tra nếu một chức năng tồn tại. May mắn thay, thật dễ dàng để viết điều đó. Lưu ý, tuy nhiên, bạn cần phải viết một đặc điểm bản thân cho mỗi tên hàm khác nhau mà bạn có thể muốn.

#include <type_traits>

template<class>
struct sfinae_true : std::true_type{};

namespace detail{
  template<class T, class A0>
  static auto test_stream(int)
      -> sfinae_true<decltype(std::declval<T>().stream(std::declval<A0>()))>;
  template<class, class A0>
  static auto test_stream(long) -> std::false_type;
} // detail::

template<class T, class Arg>
struct has_stream : decltype(detail::test_stream<T, Arg>(0)){};

Ví dụ sống.

Và để giải thích. Đầu tiên, sfinae_truelà một loại người trợ giúp, và về cơ bản nó tương đương với văn bản decltype(void(std::declval<T>().stream(a0)), std::true_type{}). Ưu điểm đơn giản là nó ngắn hơn.
Tiếp theo, struct has_stream : decltype(...)kế thừa từ một trong hai std::true_typehoặc std::false_typecuối cùng, tùy thuộc vào việc decltypekiểm tra trong test_streamthất bại hay không.
Cuối cùng, std::declvalcung cấp cho bạn một "giá trị" của bất kỳ loại nào bạn vượt qua, mà bạn không cần biết làm thế nào bạn có thể xây dựng nó. Lưu ý rằng điều này chỉ có thể có trong một bối cảnh không được đánh giá, chẳng hạn như decltype, sizeofvà các bối cảnh khác.


Lưu ý rằng decltypekhông nhất thiết là cần thiết, vì sizeof(và tất cả các bối cảnh không được đánh giá) có được sự nâng cao đó. Chỉ là nó decltypeđã cung cấp một loại và như vậy là sạch hơn. Đây là sizeofphiên bản của một trong những tình trạng quá tải:

template<class T>
void serialize_imp(std::ostream& os, T const& obj, int,
    int(*)[sizeof((os << obj),0)] = 0)
{
  os << obj;
}

Các tham số intlongvẫn còn đó cho cùng một lý do. Con trỏ mảng được sử dụng để cung cấp một bối cảnh sizeofcó thể được sử dụng.


4
Ưu điểm của decltypehơn nữa sizeoflà tạm thời không được giới thiệu bởi các quy tắc được chế tạo đặc biệt cho các lệnh gọi hàm (vì vậy bạn không phải có quyền truy cập vào hàm hủy của loại trả về và sẽ không gây ra tức thời ngầm nếu loại trả về là một lớp khởi tạo mẫu).
Julian Schaub - litb

5
Microsoft chưa triển khai Expression SFINAE trong trình biên dịch C ++. Chỉ cần hình tôi có thể giúp tiết kiệm thời gian của một số người, vì tôi đã bối rối tại sao điều này không làm việc cho tôi. Mặc dù vậy, giải pháp tuyệt vời, không thể chờ đợi để sử dụng nó trong Visual Studio!
Jonathan

3
Liên kết ví dụ đầu tiên của bạn bị hỏng
NathanOliver

1
Phải nói rằng, nó static_assert(has_stream<X, char>() == true, "fail X");sẽ biên dịch và không khẳng định vì char có thể chuyển đổi thành int, vì vậy nếu hành vi đó không muốn và muốn tất cả các loại đối số khớp với nhau thì tôi không biết làm thế nào có thể đạt được?
Gabriel

4
Nếu bạn đang bối rối như tôi đã nói về hai đối số cho dectype: dectype thực sự chỉ mất một; dấu phẩy là một toán tử ở đây. Xem stackoverflow.com/questions/16044514/ Mạnh
André

159

C ++ cho phép SFINAE được sử dụng cho việc này (lưu ý rằng với các tính năng của C ++ 11, việc này đơn giản hơn vì nó hỗ trợ SFINAE mở rộng trên các biểu thức gần như tùy ý - bên dưới được tạo để làm việc với các trình biên dịch C ++ 03 phổ biến):

#define HAS_MEM_FUNC(func, name)                                        \
    template<typename T, typename Sign>                                 \
    struct name {                                                       \
        typedef char yes[1];                                            \
        typedef char no [2];                                            \
        template <typename U, U> struct type_check;                     \
        template <typename _1> static yes &chk(type_check<Sign, &_1::func > *); \
        template <typename   > static no  &chk(...);                    \
        static bool const value = sizeof(chk<T>(0)) == sizeof(yes);     \
    }

mẫu và macro ở trên cố gắng khởi tạo một mẫu, tạo cho nó một kiểu con trỏ hàm thành viên và con trỏ hàm thành viên thực tế. Nếu các loại không phù hợp, SFINAE làm cho mẫu bị bỏ qua. Cách sử dụng như thế này:

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> void
doSomething() {
   if(has_to_string<T, std::string(T::*)()>::value) {
      ...
   } else {
      ...
   }
}

Nhưng lưu ý rằng bạn không thể chỉ gọi toStringhàm đó trong nhánh đó. vì trình biên dịch sẽ kiểm tra tính hợp lệ trong cả hai nhánh, điều đó sẽ thất bại trong trường hợp hàm không tồn tại. Một cách là sử dụng SFINAE một lần nữa (enable_if cũng có thể nhận được từ boost):

template<bool C, typename T = void>
struct enable_if {
  typedef T type;
};

template<typename T>
struct enable_if<false, T> { };

HAS_MEM_FUNC(toString, has_to_string);

template<typename T> 
typename enable_if<has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T has toString ... */
   return t->toString();
}

template<typename T> 
typename enable_if<!has_to_string<T, 
                   std::string(T::*)()>::value, std::string>::type
doSomething(T * t) {
   /* something when T doesnt have toString ... */
   return "T::toString() does not exist.";
}

Hãy vui vẻ sử dụng nó. Ưu điểm của nó là nó cũng hoạt động cho các hàm thành viên bị quá tải, và cả cho các hàm thành viên const (hãy nhớ sử dụng std::string(T::*)() constnhư kiểu con trỏ hàm thành viên sau đó!).


7
Tôi thích cách type_checkđược sử dụng để đảm bảo rằng các chữ ký đồng ý chính xác. Có cách nào để làm cho nó phù hợp với bất kỳ phương thức nào có thể được gọi theo cách mà một phương thức có chữ ký Signcó thể được gọi không? (Ví dụ if Sign= std::string(T::*)(), cho phép std::string T::toString(int default = 42, ...)khớp.)
j_random_hacker

5
Tôi chỉ tìm ra điều gì đó về điều này không rõ ràng đối với tôi, vì vậy trong trường hợp nó giúp được người khác: chk không và không cần phải xác định! Toán tử sizeof xác định kích thước đầu ra của chk mà không cần gọi chk.
SCFbler

3
@ deek0146: Có, Tkhông phải là loại nguyên thủy, bởi vì khai báo con trỏ đến phương thức T không phải là đối tượng của SFINAE và sẽ báo lỗi cho bất kỳ loại nào không thuộc lớp T. IMO, giải pháp đơn giản nhất là kết hợp với is_classkiểm tra từ tăng.
Jan Hudec

2
Làm thế nào tôi có thể làm cho công việc này nếu của tôi toStringlà một chức năng templated?
Frank

4
Đây có phải (hoặc bất cứ điều gì tương đương) trong Boost?
Dan Nissenbaum

89

C ++ 20 - requiresbiểu thức

Với C ++ 20 có các khái niệm và các công cụ như các requiresbiểu thức được tích hợp sẵn để kiểm tra sự tồn tại của hàm. Với chúng, bạn có thể viết lại optionalToStringchức năng của mình như sau:

template<class T>
std::string optionalToString(T* obj)
{
    constexpr bool has_toString = requires(const T& t) {
        t.toString();
    };

    if constexpr (has_toString)
        return obj->toString();
    else
        return "toString not defined";
}

Pre-C ++ 20 - Bộ công cụ phát hiện

N4502 đề xuất một bộ công cụ phát hiện để đưa vào thư viện chuẩn C ++ 17 mà cuối cùng đã đưa nó vào thư viện cơ bản TS v2. Nó rất có thể sẽ không bao giờ đạt được tiêu chuẩn bởi vì nó đã bị giảm bớt bởi các requiresbiểu thức kể từ đó, nhưng nó vẫn giải quyết vấn đề theo cách hơi thanh lịch. Bộ công cụ giới thiệu một số siêu liên kết, bao gồm cả các siêu dữ std::is_detectedliệu có thể được sử dụng để dễ dàng ghi các siêu dữ liệu phát hiện loại hoặc chức năng trên đầu của nó. Đây là cách bạn có thể sử dụng nó:

template<typename T>
using toString_t = decltype( std::declval<T&>().toString() );

template<typename T>
constexpr bool has_toString = std::is_detected_v<toString_t, T>;

Lưu ý rằng ví dụ trên chưa được kiểm tra. Bộ công cụ phát hiện chưa có sẵn trong các thư viện tiêu chuẩn nhưng đề xuất chứa một triển khai đầy đủ mà bạn có thể dễ dàng sao chép nếu bạn thực sự cần nó. Nó chơi rất hay với tính năng C ++ 17 if constexpr:

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr (has_toString<T>)
        return obj->toString();
    else
        return "toString not defined";
}

C ++ 14 - Boost.Hana

Boost.Hana rõ ràng dựa trên ví dụ cụ thể này và cung cấp giải pháp cho C ++ 14 trong tài liệu của mình, vì vậy tôi sẽ trích dẫn trực tiếp:

[...] Hana cung cấp một is_validchức năng có thể được kết hợp với lambdas chung C ++ 14 để có được triển khai thực hiện cùng một thứ rõ ràng hơn:

auto has_toString = hana::is_valid([](auto&& obj) -> decltype(obj.toString()) { });

Điều này để lại cho chúng ta một đối tượng hàm has_toStringtrả về việc biểu thức đã cho có hợp lệ trên đối số mà chúng ta truyền cho nó hay không. Kết quả được trả về là một IntegralConstant, vì vậy constexpr-ness không phải là vấn đề ở đây vì kết quả của hàm được biểu diễn dưới dạng một kiểu nào đó. Bây giờ, ngoài việc ít dài dòng hơn (đó là một lớp lót!), Ý định đã rõ ràng hơn nhiều. Các lợi ích khác là thực tế has_toStringcó thể được chuyển cho các thuật toán bậc cao hơn và nó cũng có thể được xác định ở phạm vi hàm, do đó không cần phải làm ô nhiễm phạm vi không gian tên với các chi tiết triển khai.

Tăng.TTI

Một bộ công cụ hơi thành ngữ khác để thực hiện kiểm tra như vậy - mặc dù kém thanh lịch hơn - là Boost.TTI , được giới thiệu trong Boost 1.54.0. Ví dụ của bạn, bạn sẽ phải sử dụng macro BOOST_TTI_HAS_MEMBER_FUNCTION. Đây là cách bạn có thể sử dụng nó:

#include <boost/tti/has_member_function.hpp>

// Generate the metafunction
BOOST_TTI_HAS_MEMBER_FUNCTION(toString)

// Check whether T has a member function toString
// which takes no parameter and returns a std::string
constexpr bool foo = has_member_function_toString<T, std::string>::value;

Sau đó, bạn có thể sử dụng boolđể tạo séc SFINAE.

Giải trình

Macro BOOST_TTI_HAS_MEMBER_FUNCTIONtạo ra siêu dữ liệu has_member_function_toStringlấy loại đã kiểm tra làm tham số mẫu đầu tiên của nó. Tham số mẫu thứ hai tương ứng với kiểu trả về của hàm thành viên và các tham số sau tương ứng với các loại tham số của hàm. Thành viên valuechứa truenếu lớp Tcó chức năng thành viên std::string toString().

Ngoài ra, has_member_function_toStringcó thể lấy một con trỏ hàm thành viên làm tham số mẫu. Do đó, có thể thay thế has_member_function_toString<T, std::string>::valuebằng has_member_function_toString<std::string T::* ()>::value.


1
ngắn gọn hơn 03
ZFY

@ZFY Tôi nghĩ rằng Boost.TTI cũng hoạt động với C ++ 03, nhưng đó là giải pháp ít thanh lịch nhất trong số rất nhiều.
Morwenn

Giải pháp C ++ 20 có thực sự hợp lệ? Tôi muốn nó - nhưng nó bị từ chối bởi g ++ và msvc - chỉ được chấp nhận bởi clang.
Bernd Baumanns

tại cppreference bạn có thể đọc: Nếu một biểu thức yêu cầu chứa các loại hoặc biểu thức không hợp lệ trong các yêu cầu của nó và nó không xuất hiện trong phần khai báo của một thực thể templated, thì chương trình không được định dạng.
Bernd Baumanns

@BerndBaumanns Thật sao? Tôi đã làm cho nó hoạt động với thân cây GCC: godbolt.org/z/CBwZdE Có lẽ bạn đúng, tôi chỉ kiểm tra xem nó có hoạt động không nhưng không kiểm tra xem nó có hợp pháp theo từ ngữ chuẩn hay không.
Morwenn

56

Mặc dù câu hỏi này đã được hai tuổi, tôi sẽ dám thêm câu trả lời của mình. Hy vọng nó sẽ làm rõ các giải pháp trước đây, xuất sắc không thể chối cãi. Tôi đã lấy những câu trả lời rất hữu ích của Nicola Bonelli và Johannes Schaub và hợp nhất chúng thành một giải pháp, IMHO, dễ đọc hơn, rõ ràng hơn và không cần typeofgia hạn:

template <class Type>
class TypeHasToString
{
    // This type won't compile if the second template parameter isn't of type T,
    // so I can put a function pointer type in the first parameter and the function
    // itself in the second thus checking that the function has a specific signature.
    template <typename T, T> struct TypeCheck;

    typedef char Yes;
    typedef long No;

    // A helper struct to hold the declaration of the function pointer.
    // Change it if the function signature changes.
    template <typename T> struct ToString
    {
        typedef void (T::*fptr)();
    };

    template <typename T> static Yes HasToString(TypeCheck< typename ToString<T>::fptr, &T::toString >*);
    template <typename T> static No  HasToString(...);

public:
    static bool const value = (sizeof(HasToString<Type>(0)) == sizeof(Yes));
};

Tôi đã kiểm tra nó với gcc 4.1.2. Khoản tín dụng chủ yếu dành cho Nicola Bonelli và Johannes Schaub, vì vậy hãy cho họ bỏ phiếu nếu câu trả lời của tôi giúp bạn :)


1
Chỉ tự hỏi, điều này có làm bất cứ điều gì mà giải pháp của Konrad Rudolph dưới đây không làm được không?
Alastair Irvine

3
@AlastairIrvine, giải pháp này ẩn tất cả logic bên trong, Konrad đặt một số gánh nặng lên người dùng. Mặc dù ngắn và dễ đọc hơn nhiều, giải pháp của Konrad yêu cầu một chuyên môn mẫu riêng cho từng lớp có toString. Nếu bạn viết một thư viện chung, muốn làm việc với bất kỳ lớp nào ngoài đó (nghĩ về thứ gì đó như boost), thì việc yêu cầu người dùng xác định các chuyên ngành bổ sung của một số mẫu tối nghĩa có thể không được chấp nhận. Đôi khi, nên viết một mã rất phức tạp để giữ cho giao diện chung đơn giản nhất có thể.
FireAp4 14/12/13

30

Một giải pháp đơn giản cho C ++ 11:

template<class T>
auto optionalToString(T* obj)
 -> decltype(  obj->toString()  )
{
    return     obj->toString();
}
auto optionalToString(...) -> string
{
    return "toString not defined";
}

Cập nhật, 3 năm sau: (và điều này chưa được kiểm tra). Để kiểm tra sự tồn tại, tôi nghĩ rằng điều này sẽ hoạt động:

template<class T>
constexpr auto test_has_toString_method(T* obj)
 -> decltype(  obj->toString() , std::true_type{} )
{
    return     obj->toString();
}
constexpr auto test_has_toString_method(...) -> std::false_type
{
    return "toString not defined";
}

4
Điều này đơn giản và thanh lịch, nhưng nói đúng ra không trả lời câu hỏi của OP: bạn không cho phép người gọi kiểm tra sự tồn tại của một chức năng, bạn luôn cung cấp nó. Nhưng dù sao cũng tốt.
Adrian W

@AdrianW, điểm tốt. Tôi đã cập nhật câu trả lời của mình. Tôi chưa thử nó mặc dù
Aaron McDaid

Trong trường hợp nó giúp được người khác, tôi không thể thực hiện công việc này mà không template<typename>bị quá tải đột ngột: nó không được xem xét để giải quyết.
Phòng thí nghiệm Cobotica

Một lần nữa, đây là C ++ 11 không hợp lệ.
Peter

29

Đây là những đặc điểm loại có sẵn cho. Thật không may, chúng phải được xác định bằng tay. Trong trường hợp của bạn, hãy tưởng tượng như sau:

template <typename T>
struct response_trait {
    static bool const has_tostring = false;
};

template <>
struct response_trait<your_type_with_tostring> {
    static bool const has_tostring = true;
}

5
bạn nên ưu tiên enum cho các đặc điểm thay vì hằng số tĩnh: "Các thành viên hằng số tĩnh là các giá trị, buộc trình biên dịch khởi tạo và phân bổ định nghĩa cho thành viên tĩnh. Do đó, việc tính toán không còn bị giới hạn trong thời gian biên dịch" thuần túy " " hiệu ứng."
Özgür

5
"Các giá trị liệt kê không phải là giá trị (nghĩa là chúng không có địa chỉ). Vì vậy, khi bạn chuyển chúng" bằng cách tham chiếu, "không có bộ nhớ tĩnh nào được sử dụng. Nó gần như chính xác như bạn đã chuyển giá trị được tính như một chữ Những cân nhắc này thúc đẩy chúng tôi sử dụng các giá trị liệt kê "Các mẫu C ++: Hướng dẫn hoàn chỉnh
zgür

22
Comptrol: không, đoạn trích dẫn không áp dụng ở đây vì hằng số tĩnh kiểu nguyên là trường hợp đặc biệt! Họ cư xử chính xác như một enum ở đây và là cách ưa thích. Bản hack enum cũ chỉ cần thiết trên các trình biên dịch không tuân theo tiêu chuẩn C ++.
Konrad Rudolph

3
@Roger Pate: Không hoàn toàn. Được sử dụng trong chương trình, ở đây rõ ràng là đồng nghĩa với những người tham khảo. Cách đọc phổ biến của đoạn này và một đoạn được thực hiện bởi tất cả các trình biên dịch C ++ hiện đại, là bạn có thể lấy giá trị của hằng số tĩnh mà không cần phải khai báo nó (câu trước nói: Giáo chí). Bạn chỉ cần xác định nó nếu bạn lấy địa chỉ của nó (rõ ràng thông qua &T::xhoặc ngầm định bằng cách ràng buộc nó với một tham chiếu).
Konrad Rudolph


25

Vâng, câu hỏi này đã có một danh sách dài các câu trả lời, nhưng tôi muốn nhấn mạnh nhận xét từ Morwenn: có một đề xuất cho C ++ 17 làm cho nó thực sự đơn giản hơn nhiều. Xem N4502 để biết chi tiết, nhưng như một ví dụ khép kín, hãy xem xét những điều sau đây.

Phần này là phần không đổi, đặt nó trong một tiêu đề.

// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;

// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};

// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};

sau đó là phần biến, nơi bạn chỉ định những gì bạn đang tìm kiếm (một loại, một loại thành viên, một chức năng, một chức năng thành viên, vv). Trong trường hợp của OP:

template <typename T>
using toString_t = decltype(std::declval<T>().toString());

template <typename T>
using has_toString = detect<T, toString_t>;

Ví dụ sau, được lấy từ N4502 , cho thấy một thăm dò phức tạp hơn:

// Archetypal expression for assignment operation.
template <typename T>
using assign_t = decltype(std::declval<T&>() = std::declval<T const &>())

// Trait corresponding to that archetype.
template <typename T>
using is_assignable = detect<T, assign_t>;

So với các triển khai khác được mô tả ở trên, cách này khá đơn giản: một bộ công cụ giảm ( void_tdetect) đủ, không cần macro lông. Ngoài ra, nó đã được báo cáo (xem N4502 ) rằng nó hiệu quả hơn (thời gian biên dịch và tiêu thụ bộ nhớ của trình biên dịch) so với các phương pháp trước đây.

Đây là một ví dụ sống . Nó hoạt động tốt với Clang, nhưng thật không may, các phiên bản GCC trước 5.1 đã tuân theo một cách hiểu khác về tiêu chuẩn C ++ 11 khiến void_tnó không hoạt động như mong đợi. Yakk đã cung cấp giải pháp chung: sử dụng định nghĩa sau void_t( void_t trong danh sách tham số hoạt động nhưng không phải là kiểu trả về ):

#if __GNUC__ < 5 && ! defined __clang__
// https://stackoverflow.com/a/28967049/1353549
template <typename...>
struct voider
{
  using type = void;
};
template <typename...Ts>
using void_t = typename voider<Ts...>::type;
#else
template <typename...>
using void_t = void;
#endif

Có thể mở rộng nó để phát hiện các chức năng không phải thành viên?
plasmacel

Vâng, chắc chắn rồi. Xem xét kỹ các ví dụ: về cơ bản bạn cung cấp một biểu thức và kiểm tra xem nó có hợp lệ không. Không có gì đòi hỏi biểu thức này chỉ là về một cuộc gọi chức năng thành viên.
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/ con / 2015 / n4502.pdf ) là cách của tương lai ... Tôi đang tìm kiếm một cách gọn gàng để phát hiện mọi thứ trên các loại và N4502 là cách đi.
tlonuk

11

Đây là một giải pháp C ++ 11 cho vấn đề chung nếu "Nếu tôi đã làm X, nó có biên dịch không?"

template<class> struct type_sink { typedef void type; }; // consumes a type, and makes it `void`
template<class T> using type_sink_t = typename type_sink<T>::type;
template<class T, class=void> struct has_to_string : std::false_type {}; \
template<class T> struct has_to_string<
  T,
  type_sink_t< decltype( std::declval<T>().toString() ) >
>: std::true_type {};

Đặc điểm has_to_stringđó has_to_string<T>::valuetruenếu và chỉ khi Tcó một phương thức .toStringcó thể được gọi với 0 đối số trong ngữ cảnh này.

Tiếp theo, tôi sẽ sử dụng việc gửi thẻ:

namespace details {
  template<class T>
  std::string optionalToString_helper(T* obj, std::true_type /*has_to_string*/) {
    return obj->toString();
  }
  template<class T>
  std::string optionalToString_helper(T* obj, std::false_type /*has_to_string*/) {
    return "toString not defined";
  }
}
template<class T>
std::string optionalToString(T* obj) {
  return details::optionalToString_helper( obj, has_to_string<T>{} );
}

có xu hướng duy trì nhiều hơn các biểu thức SFINAE phức tạp.

Bạn có thể viết những đặc điểm này bằng một macro nếu bạn thấy mình làm điều đó rất nhiều, nhưng chúng tương đối đơn giản (mỗi dòng một vài) vì vậy có thể không đáng:

#define MAKE_CODE_TRAIT( TRAIT_NAME, ... ) \
template<class T, class=void> struct TRAIT_NAME : std::false_type {}; \
template<class T> struct TRAIT_NAME< T, type_sink_t< decltype( __VA_ARGS__ ) > >: std::true_type {};

những gì ở trên là tạo ra một macro MAKE_CODE_TRAIT. Bạn chuyển cho nó tên của đặc điểm bạn muốn và một số mã có thể kiểm tra loại T. Như vậy:

MAKE_CODE_TRAIT( has_to_string, std::declval<T>().toString() )

tạo ra các đặc điểm trên.

Bên cạnh đó, kỹ thuật trên là một phần của cái mà MS gọi là "biểu thức SFINAE", và trình biên dịch 2013 của họ thất bại khá khó khăn.

Lưu ý rằng trong C ++ 1y có thể thực hiện cú pháp sau:

template<class T>
std::string optionalToString(T* obj) {
  return compiled_if< has_to_string >(*obj, [&](auto&& obj) {
    return obj.toString();
  }) *compiled_else ([&]{ 
    return "toString not defined";
  });
}

đó là một nhánh biên dịch nội tuyến có điều kiện lạm dụng rất nhiều tính năng của C ++. Làm như vậy có lẽ không đáng, vì lợi ích (của mã là nội tuyến) không đáng giá (bên cạnh không ai hiểu cách thức hoạt động của nó), nhưng sự tồn tại của giải pháp trên có thể được quan tâm.


Điều này có xử lý các trường hợp tư nhân?
tháp120

@ tower120 Tôi sẽ phải thử nghiệm: cách các mẫu tương tác với private / public / bảo vệ là một điều tối nghĩa với tôi. Nó sẽ không quan trọng nơi bạn gọi has_to_stringtuy nhiên.
Yakk - Adam Nevraumont

nhưng bạn biết đấy, nếu nhìn từ phía bên kia ... Chúng ta có thể tiếp cận các thành viên được bảo vệ từ lớp Derogen. Có lẽ nếu đặt tất cả những thứ này vào lớp bên trong và chuyển đổi từ các cấu trúc thành các hàm constexpr ...
tower120

Ở đây, hãy nhìn vào coliru.stacked-crooking.com/a/ee94d16e7c07e093 Tôi chỉ không thể làm cho nó constexpr
tower120

@ tower120 C ++ 1y làm cho nó hoạt động: coliru.stacked-crooking.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

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
Bạn có biết tại sao nếu chúng ta thay đổi sig_check<func_sig, &T::func_name>thành kiểm tra chức năng miễn phí: sig_check<func_sig, &func_name>nó không được xây dựng với "định danh không khai báo" đề cập đến tên của chức năng mà chúng ta muốn kiểm tra? bởi vì tôi mong đợi SFINAE biến nó thành lỗi KHÔNG, nó chỉ xảy ra với các thành viên, tại sao không có chức năng miễn phí?
v.oddou 6/03/2015

Tôi giả sử nó sẽ có một cái gì đó để làm với thực tế là một hàm miễn phí không phải là một lớp hoặc cấu trúc. Kỹ thuật suy luận sự hiện diện của một thành viên thực sự tập trung vào cơ chế đa kế thừa trong C ++ buộc sự mơ hồ giữa một lớp sơ khai chỉ tồn tại cho mục đích lưu trữ thành viên mà bạn đang kiểm tra so với lớp bạn thực sự đang kiểm tra thành viên trong. Đó là một câu hỏi thú vị, mặc dù, đã không nghĩ về nó. Bạn có thể kiểm tra xung quanh các kỹ thuật kiểm tra thành viên C ++ 11/14 khác, tôi đã thấy một số điều thông minh trong tiêu chuẩn mới.
Brett Rossier

Cảm ơn câu trả lời của bạn, tôi nghĩ rằng tôi có thể phải kiểm tra sâu hơn thông tin mà bạn cung cấp về thừa kế, bởi vì cho đến bây giờ tôi không thấy bất kỳ mối tương quan nào giữa việc chỉ dựa vào SFINAE để đưa ra một biểu thức không thể thể hiện chính xác quyền truy cập vào một thành viên trong một tham số kiểu mẫu và nhiều kế thừa. Nhưng tôi hoàn toàn tin rằng trong C ++, ngay cả những khái niệm xa vời cũng có thể đổ máu cho nhau. Bây giờ đối với các chức năng miễn phí, câu hỏi này rất thú vị: stackoverflow.com/questions/26744589 TC trả lời dường như sử dụng một mẹo khai báo một hình nộm để tránh "định danh không khai báo"
v.oddou

8

Tôi đã viết một câu trả lời cho vấn đề này trong một chủ đề khác (không giống như các giải pháp ở trên) cũng kiểm tra các chức năng thành viên được kế thừa:

SFINAE để kiểm tra các chức năng thành viên được kế thừa

Dưới đây là một số ví dụ từ giải pháp đó:

Ví dụ 1:

Chúng tôi đang kiểm tra một thành viên có chữ ký sau: T::const_iterator begin() const

template<class T> struct has_const_begin
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U> 
    static Yes test(U const * data, 
                    typename std::enable_if<std::is_same<
                             typename U::const_iterator, 
                             decltype(data->begin())
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_const_begin::test((typename std::remove_reference<T>::type*)0));
};

Xin lưu ý rằng nó thậm chí còn kiểm tra cả hằng số của phương thức và cũng hoạt động với các kiểu nguyên thủy. (Ý tôi has_const_begin<int>::valuelà sai và không gây ra lỗi thời gian biên dịch.)

Ví dụ 2

Bây giờ chúng tôi đang tìm kiếm chữ ký: void foo(MyClass&, unsigned)

template<class T> struct has_foo
{
    typedef char (&Yes)[1];
    typedef char (&No)[2];

    template<class U>
    static Yes test(U * data, MyClass* arg1 = 0,
                    typename std::enable_if<std::is_void<
                             decltype(data->foo(*arg1, 1u))
                    >::value>::type * = 0);
    static No test(...);
    static const bool value = sizeof(Yes) == sizeof(has_foo::test((typename std::remove_reference<T>::type*)0));
};

Xin lưu ý rằng MyClass không phải là cấu trúc mặc định hoặc để đáp ứng bất kỳ khái niệm đặc biệt nào. Kỹ thuật này cũng hoạt động với các thành viên mẫu.

Tôi háo hức chờ đợi ý kiến ​​liên quan đến việc này.


7

Bây giờ đây là một câu đố nhỏ tốt đẹp - câu hỏi tuyệt vời!

Đây là một giải pháp thay thế cho giải pháp của Nicola Bonelli không dựa vào typeoftoán tử không chuẩn .

Thật không may, nó không hoạt động trên GCC (MinGW) 3.4.5 hoặc Digital Mars 8.42n, nhưng nó hoạt động trên tất cả các phiên bản MSVC (bao gồm cả VC6) và trên Comeau C ++.

Khối bình luận dài hơn có các chi tiết về cách thức hoạt động (hoặc được cho là hoạt động). Như đã nói, tôi không chắc hành vi nào tuân thủ tiêu chuẩn - Tôi hoan nghênh bình luận về điều đó.


cập nhật - ngày 7 tháng 11 năm 2008:

Có vẻ như trong khi mã này đúng về mặt cú pháp, hành vi mà MSVC và Comeau C ++ thể hiện không tuân theo tiêu chuẩn (nhờ Leon Timmermanslitb đã chỉ cho tôi đi đúng hướng). Tiêu chuẩn C ++ 03 cho biết như sau:

14.6.2 Tên phụ thuộc [temp.dep]

Đoạn 3

Trong định nghĩa của mẫu lớp hoặc thành viên của mẫu lớp, nếu một lớp cơ sở của mẫu lớp phụ thuộc vào tham số mẫu, thì phạm vi lớp cơ sở không được kiểm tra trong quá trình tra cứu tên không đủ tiêu chuẩn tại điểm định nghĩa của lớp mẫu hoặc thành viên hoặc trong thời gian khởi tạo mẫu lớp hoặc thành viên.

Vì vậy, có vẻ như khi MSVC hoặc Comeau xem xét toString()chức năng thành viên của Tviệc thực hiện tra cứu tên tại trang web cuộc gọi doToString()khi mẫu được khởi tạo, điều đó không chính xác (mặc dù đó thực sự là hành vi tôi đang tìm kiếm trong trường hợp này).

Hành vi của GCC và Digital Mars có vẻ đúng - trong cả hai trường hợp, toString()chức năng không phải là thành viên bị ràng buộc với cuộc gọi.

Chuột - Tôi nghĩ rằng tôi có thể đã tìm thấy một giải pháp thông minh, thay vào đó tôi phát hiện ra một vài lỗi trình biên dịch ...


#include <iostream>
#include <string>

struct Hello
{
    std::string toString() {
        return "Hello";
    }
};

struct Generic {};


// the following namespace keeps the toString() method out of
//  most everything - except the other stuff in this
//  compilation unit

namespace {
    std::string toString()
    {
        return "toString not defined";
    }

    template <typename T>
    class optionalToStringImpl : public T
    {
    public:
        std::string doToString() {

            // in theory, the name lookup for this call to 
            //  toString() should find the toString() in 
            //  the base class T if one exists, but if one 
            //  doesn't exist in the base class, it'll 
            //  find the free toString() function in 
            //  the private namespace.
            //
            // This theory works for MSVC (all versions
            //  from VC6 to VC9) and Comeau C++, but
            //  does not work with MinGW 3.4.5 or 
            //  Digital Mars 8.42n
            //
            // I'm honestly not sure what the standard says 
            //  is the correct behavior here - it's sort 
            //  of like ADL (Argument Dependent Lookup - 
            //  also known as Koenig Lookup) but without
            //  arguments (except the implied "this" pointer)

            return toString();
        }
    };
}

template <typename T>
std::string optionalToString(T & obj)
{
    // ugly, hacky cast...
    optionalToStringImpl<T>* temp = reinterpret_cast<optionalToStringImpl<T>*>( &obj);

    return temp->doToString();
}



int
main(int argc, char *argv[])
{
    Hello helloObj;
    Generic genericObj;

    std::cout << optionalToString( helloObj) << std::endl;
    std::cout << optionalToString( genericObj) << std::endl;
    return 0;
}

1
Không, nó không tuân thủ tiêu chuẩn, mặc dù tôi nghĩ rằng nó sẽ hoạt động trong GCC nếu bạn bật tùy chọn -fpermissive.
Leon Timmermans

Tôi biết các ý kiến ​​không cung cấp nhiều phòng, nhưng bạn có thể chỉ ra thông tin về lý do tại sao nó không tuân thủ tiêu chuẩn không? (Tôi không tranh cãi - Tôi tò mò)
Michael Burr

Mike B: tiêu chuẩn nói trong 3.10 p15: "Nếu một chương trình cố gắng truy cập giá trị được lưu trữ của một đối tượng thông qua một giá trị khác với một trong các loại sau đây thì hành vi không được xác định" và danh sách đó thực sự không bao gồm trường hợp bạn làm
Julian Schaub - litb

4
Tôi không chắc tại sao nó không thêm nhận xét khác của tôi: cuộc gọi toString của bạn không đủ điều kiện. do đó, nó sẽ luôn gọi hàm miễn phí và không bao giờ là hàm trong cơ sở, vì lớp cơ sở phụ thuộc vào tham số kiểu mẫu.
Julian Schaub - litb

@litb: Cảm ơn các con trỏ. Tôi không nghĩ 3.10 áp dụng ở đây. Cuộc gọi đến toString () bên trong doToString () không phải là "truy cập giá trị được lưu trữ của một đối tượng thông qua một giá trị". Nhưng nhận xét thứ 2 của bạn là chính xác. Tôi sẽ cập nhật câu trả lời.
Michael Burr

6

Giải pháp C ++ tiêu chuẩn được trình bày ở đây bởi litb sẽ không hoạt động như mong đợi nếu phương thức này được định nghĩa trong một lớp cơ sở.

Để biết giải pháp xử lý tình huống này, hãy tham khảo:

Trong tiếng Nga: http://www.rsdn.ru/forum/message/2759773.1.aspx

Bản dịch tiếng Anh của Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

Đó là thông minh điên rồ. Tuy nhiên, một vấn đề với giải pháp này là đưa ra lỗi trình biên dịch nếu loại đang được kiểm tra là loại không thể được sử dụng làm lớp cơ sở (ví dụ: kiểu nguyên thủy)

Trong Visual Studio, tôi nhận thấy rằng nếu làm việc với phương thức không có đối số, thì cần thêm một cặp dự phòng () xung quanh các đối số để suy ra () trong biểu thức sizeof.


Hmm, đã phát triển phiên bản của riêng mình bằng cách đăng các ý tưởng đó, tôi thấy ý tưởng này có một số nhược điểm khác nên tôi đã xóa mã khỏi câu trả lời của mình một lần nữa. Một là tất cả các chức năng phải được công khai trong loại mục tiêu. Vì vậy, bạn không thể kiểm tra hàm "f" trong trường hợp này: struct g { void f(); private: void f(int); };bởi vì một trong các hàm là riêng tư (điều này là do mã này using g::f;làm cho nó bị lỗi nếu fkhông thể truy cập được).
Julian Schaub - litb

6

MSVC có các từ khóa __if_exists và __if_not_exists ( Doc ). Cùng với cách tiếp cận kiểu SF-SFINA của Nicola, tôi có thể tạo một kiểm tra cho GCC và MSVC giống như OP tìm kiếm.

Cập nhật: Nguồn có thể được tìm thấy ở đây


6

Một ví dụ sử dụng SFINAE và chuyên môn hóa một phần mẫu, bằng cách viết một Has_fookiểm tra khái niệm:

#include <type_traits>
struct A{};

struct B{ int foo(int a, int b);};

struct C{void foo(int a, int b);};

struct D{int foo();};

struct E: public B{};

// available in C++17 onwards as part of <type_traits>
template<typename...>
using void_t = void;

template<typename T, typename = void> struct Has_foo: std::false_type{};

template<typename T> 
struct Has_foo<T, void_t<
    std::enable_if_t<
        std::is_same<
            int, 
            decltype(std::declval<T>().foo((int)0, (int)0))
        >::value
    >
>>: std::true_type{};


static_assert(not Has_foo<A>::value, "A does not have a foo");
static_assert(Has_foo<B>::value, "B has a foo");
static_assert(not Has_foo<C>::value, "C has a foo with the wrong return. ");
static_assert(not Has_foo<D>::value, "D has a foo with the wrong arguments. ");
static_assert(Has_foo<E>::value, "E has a foo since it inherits from B");

5

Tôi đã sửa đổi giải pháp được cung cấp trong https://stackoverflow.com/a/264088/2712152 để làm cho nó chung chung hơn một chút. Ngoài ra, vì nó không sử dụng bất kỳ tính năng mới nào của C ++ 11, chúng tôi có thể sử dụng nó với các trình biên dịch cũ và cũng sẽ hoạt động với msvc. Nhưng các trình biên dịch nên cho phép C99 sử dụng điều này vì nó sử dụng các macro biến đổi.

Macro sau có thể được sử dụng để kiểm tra xem một lớp cụ thể có typedef cụ thể hay không.

/** 
 * @class      : HAS_TYPEDEF
 * @brief      : This macro will be used to check if a class has a particular
 * typedef or not.
 * @param typedef_name : Name of Typedef
 * @param name  : Name of struct which is going to be run the test for
 * the given particular typedef specified in typedef_name
 */
#define HAS_TYPEDEF(typedef_name, name)                           \
   template <typename T>                                          \
   struct name {                                                  \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U>                                       \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<typename _1::typedef_name>*);    \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Macro sau có thể được sử dụng để kiểm tra xem một lớp cụ thể có hàm thành viên cụ thể hay không với bất kỳ số lượng đối số đã cho nào.

/** 
 * @class      : HAS_MEM_FUNC
 * @brief      : This macro will be used to check if a class has a particular
 * member function implemented in the public section or not. 
 * @param func : Name of Member Function
 * @param name : Name of struct which is going to be run the test for
 * the given particular member function name specified in func
 * @param return_type: Return type of the member function
 * @param ellipsis(...) : Since this is macro should provide test case for every
 * possible member function we use variadic macros to cover all possibilities
 */
#define HAS_MEM_FUNC(func, name, return_type, ...)                \
   template <typename T>                                          \
   struct name {                                                  \
      typedef return_type (T::*Sign)(__VA_ARGS__);                \
      typedef char yes[1];                                        \
      typedef char no[2];                                         \
      template <typename U, U>                                    \
      struct type_check;                                          \
      template <typename _1>                                      \
      static yes& chk(type_check<Sign, &_1::func>*);              \
      template <typename>                                         \
      static no& chk(...);                                        \
      static bool const value = sizeof(chk<T>(0)) == sizeof(yes); \
   }

Chúng tôi có thể sử dụng 2 macro ở trên để thực hiện kiểm tra has_typedef và has_mem_func là:

class A {
public:
  typedef int check;
  void check_function() {}
};

class B {
public:
  void hello(int a, double b) {}
  void hello() {}
};

HAS_MEM_FUNC(check_function, has_check_function, void, void);
HAS_MEM_FUNC(hello, hello_check, void, int, double);
HAS_MEM_FUNC(hello, hello_void_check, void, void);
HAS_TYPEDEF(check, has_typedef_check);

int main() {
  std::cout << "Check Function A:" << has_check_function<A>::value << std::endl;
  std::cout << "Check Function B:" << has_check_function<B>::value << std::endl;
  std::cout << "Hello Function A:" << hello_check<A>::value << std::endl;
  std::cout << "Hello Function B:" << hello_check<B>::value << std::endl;
  std::cout << "Hello void Function A:" << hello_void_check<A>::value << std::endl;
  std::cout << "Hello void Function B:" << hello_void_check<B>::value << std::endl;
  std::cout << "Check Typedef A:" << has_typedef_check<A>::value << std::endl;
  std::cout << "Check Typedef B:" << has_typedef_check<B>::value << std::endl;
}

Bạn có thể cải thiện điều này để hỗ trợ các chức năng thành viên với các đối số mẫu. Thay đổi mẫu <typename T> thành template <typename T, typename ... Args>, sau đó bạn có thể sử dụng "Args ..." trong elipsis macro của mình để tạo một cấu trúc kiểm tra với các đối số mẫu matrixdic. ví dụ. Phát hiện phương thức "void onNext (const T &)" HAS_MEM_FUNC( onNext, has_memberfn_onNext, void, Args... ); ...template <typename V> struct Foo { void onNext(const V &); static_assert< has_memberfn_onNext<Foo<V>,const V &>::value, "API fail" ); };
ACyclic

4

Lạ không ai đề xuất thủ thuật hay sau đây tôi từng thấy trên chính trang này:

template <class T>
struct has_foo
{
    struct S { void foo(...); };
    struct derived : S, T {};

    template <typename V, V> struct W {};

    template <typename X>
    char (&test(W<void (X::*)(), &X::foo> *))[1];

    template <typename>
    char (&test(...))[2];

    static const bool value = sizeof(test<derived>(0)) == 1;
};

Bạn phải chắc chắn rằng T là một lớp. Có vẻ như sự mơ hồ trong việc tra cứu foo là một thất bại thay thế. Tôi đã làm cho nó hoạt động trên gcc, mặc dù không chắc nó là tiêu chuẩn.


3

Mẫu chung có thể được sử dụng để kiểm tra nếu một số "tính năng" được hỗ trợ bởi loại:

#include <type_traits>

template <template <typename> class TypeChecker, typename Type>
struct is_supported
{
    // these structs are used to recognize which version
    // of the two functions was chosen during overload resolution
    struct supported {};
    struct not_supported {};

    // this overload of chk will be ignored by SFINAE principle
    // if TypeChecker<Type_> is invalid type
    template <typename Type_>
    static supported chk(typename std::decay<TypeChecker<Type_>>::type *);

    // ellipsis has the lowest conversion rank, so this overload will be
    // chosen during overload resolution only if the template overload above is ignored
    template <typename Type_>
    static not_supported chk(...);

    // if the template overload of chk is chosen during
    // overload resolution then the feature is supported
    // if the ellipses overload is chosen the the feature is not supported
    static constexpr bool value = std::is_same<decltype(chk<Type>(nullptr)),supported>::value;
};

Mẫu kiểm tra xem có phương thức foonào tương thích với chữ ký khôngdouble(const char*)

// if T doesn't have foo method with the signature that allows to compile the bellow
// expression then instantiating this template is Substitution Failure (SF)
// which Is Not An Error (INAE) if this happens during overload resolution
template <typename T>
using has_foo = decltype(double(std::declval<T>().foo(std::declval<const char*>())));

Ví dụ

// types that support has_foo
struct struct1 { double foo(const char*); };            // exact signature match
struct struct2 { int    foo(const std::string &str); }; // compatible signature
struct struct3 { float  foo(...); };                    // compatible ellipsis signature
struct struct4 { template <typename T>
                 int    foo(T t); };                    // compatible template signature

// types that do not support has_foo
struct struct5 { void        foo(const char*); }; // returns void
struct struct6 { std::string foo(const char*); }; // std::string can't be converted to double
struct struct7 { double      foo(      int *); }; // const char* can't be converted to int*
struct struct8 { double      bar(const char*); }; // there is no foo method

int main()
{
    std::cout << std::boolalpha;

    std::cout << is_supported<has_foo, int    >::value << std::endl; // false
    std::cout << is_supported<has_foo, double >::value << std::endl; // false

    std::cout << is_supported<has_foo, struct1>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct2>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct3>::value << std::endl; // true
    std::cout << is_supported<has_foo, struct4>::value << std::endl; // true

    std::cout << is_supported<has_foo, struct5>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct6>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct7>::value << std::endl; // false
    std::cout << is_supported<has_foo, struct8>::value << std::endl; // false

    return 0;
}

http://coliru.stacked-crooking.com/a/83c6a631ed42cea4


Có cách nào để nội tuyến has_foovào cuộc gọi mẫu của is_supported. Những gì tôi muốn là gọi một cái gì đó như : std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. Lý do cho điều này, tôi muốn xác định một has_foocho mỗi chữ ký chức năng khác nhau mà tôi muốn kiểm tra trước khi tôi có thể kiểm tra chức năng?
CJCombrink

2

Làm thế nào về giải pháp này?

#include <type_traits>

template <typename U, typename = void> struct hasToString : std::false_type { };

template <typename U>
struct hasToString<U,
  typename std::enable_if<bool(sizeof(&U::toString))>::type
> : std::true_type { };

Thất bại nếu toStringbị quá tải, như &U::toStringlà mơ hồ.
Yakk - Adam Nevraumont

@Yakk Tôi nghĩ rằng một diễn viên có thể khắc phục vấn đề này.
dùng1095108

2

Có rất nhiều câu trả lời ở đây, nhưng tôi đã thất bại, khi tìm một phiên bản, thực hiện theo thứ tự độ phân giải phương thức thực , trong khi không sử dụng bất kỳ tính năng c ++ mới hơn nào (chỉ sử dụng các tính năng c ++ 98).
Lưu ý: Phiên bản này được thử nghiệm và hoạt động với vc ++ 2013, g ++ 5.2.0 và trình biên dịch trực tuyến.

Vì vậy, tôi đã đưa ra một phiên bản, chỉ sử dụng sizeof ():

template<typename T> T declval(void);

struct fake_void { };
template<typename T> T &operator,(T &,fake_void);
template<typename T> T const &operator,(T const &,fake_void);
template<typename T> T volatile &operator,(T volatile &,fake_void);
template<typename T> T const volatile &operator,(T const volatile &,fake_void);

struct yes { char v[1]; };
struct no  { char v[2]; };
template<bool> struct yes_no:yes{};
template<> struct yes_no<false>:no{};

template<typename T>
struct has_awesome_member {
 template<typename U> static yes_no<(sizeof((
   declval<U>().awesome_member(),fake_void()
  ))!=0)> check(int);
 template<typename> static no check(...);
 enum{value=sizeof(check<T>(0)) == sizeof(yes)};
};


struct foo { int awesome_member(void); };
struct bar { };
struct foo_void { void awesome_member(void); };
struct wrong_params { void awesome_member(int); };

static_assert(has_awesome_member<foo>::value,"");
static_assert(!has_awesome_member<bar>::value,"");
static_assert(has_awesome_member<foo_void>::value,"");
static_assert(!has_awesome_member<wrong_params>::value,"");

Bản demo trực tiếp (với kiểm tra loại trả về mở rộng và cách giải quyết vc ++ 2010): http://cpp.sh/5b2vs

Không có nguồn, như tôi đã đưa ra nó.

Khi chạy bản demo trực tiếp trên trình biên dịch g ++, xin lưu ý rằng kích thước mảng bằng 0 được cho phép, có nghĩa là static_assert được sử dụng sẽ không gây ra lỗi trình biên dịch, ngay cả khi nó bị lỗi.
Một cách giải quyết thường được sử dụng là thay thế 'typedef' trong macro bằng 'extern'.


Không, nhưng tôi tự khai báo và nó không sử dụng giá trị (nhìn vào đầu mã của tôi). Hoặc bạn chỉ có thể thuyết phục bản thân và thử bản demo trực tiếp ở chế độ c ++ 98. PS: static_assert cũng không phải là c ++ 98, nhưng có những cách giải quyết (bản demo trực tiếp)
user3296587

ôi! bỏ lỡ điều đó :-)
Ian Ni-Lewis

Khẳng định tĩnh của bạn không hoạt động. Bạn cần sử dụng kích thước mảng -1 thay vì 0 (thử đặt static_assert(false);). Tôi đã sử dụng điều này liên quan đến CRTP nơi tôi muốn xác định xem lớp dẫn xuất có một chức năng cụ thể hay không - hóa ra nó không hoạt động, nhưng các xác nhận của bạn luôn được thông qua. Tôi đã mất một số tóc cho cái đó.
con lợn

Tôi giả sử bạn đang sử dụng g ++. Xin lưu ý rằng gcc / g ++ có phần mở rộng cho phép mảng có kích thước bằng không ( gcc.gnu.org/onlinesocs/gcc/Zero-Lạng.html )
user3296587

Bạn có thể viết lại cái này để không làm quá tải toán tử không? vd: chọn toán tử khác? Ngoài ra, tránh ô nhiễm không gian tên với bất cứ điều gì khác ngoài has_awclaw_member?
einpoklum

1

Đây là phiên bản của tôi xử lý tất cả các tình trạng quá tải chức năng thành viên có thể có tùy ý, bao gồm các hàm thành viên mẫu, có thể với các đối số mặc định. Nó phân biệt 3 kịch bản loại trừ lẫn nhau khi thực hiện một hàm thành viên gọi đến một số loại lớp, với các loại đối số đã cho: (1) hợp lệ hoặc (2) mơ hồ hoặc (3) không khả thi. Ví dụ sử dụng:

#include <string>
#include <vector>

HAS_MEM(bar)
HAS_MEM_FUN_CALL(bar)

struct test
{
   void bar(int);
   void bar(double);
   void bar(int,double);

   template < typename T >
   typename std::enable_if< not std::is_integral<T>::value >::type
   bar(const T&, int=0){}

   template < typename T >
   typename std::enable_if< std::is_integral<T>::value >::type
   bar(const std::vector<T>&, T*){}

   template < typename T >
   int bar(const std::string&, int){}
};

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

int main(int argc, const char * argv[])
{
   static_assert( has_mem_bar<test>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(char const*,long)>::value , "");
   static_assert( has_valid_mem_fun_call_bar<test(std::string&,long)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(std::vector<int>, int*)>::value , "");
   static_assert( has_no_viable_mem_fun_call_bar<test(std::vector<double>, double*)>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int)>::value , "");
   static_assert( std::is_same<void,result_of_mem_fun_call_bar<test(int)>::type>::value , "");

   static_assert( has_valid_mem_fun_call_bar<test(int,double)>::value , "");
   static_assert( not has_valid_mem_fun_call_bar<test(int,double,int)>::value , "");

   static_assert( not has_ambiguous_mem_fun_call_bar<test(double)>::value , "");
   static_assert( has_ambiguous_mem_fun_call_bar<test(unsigned)>::value , "");

   static_assert( has_viable_mem_fun_call_bar<test(unsigned)>::value , "");
   static_assert( has_viable_mem_fun_call_bar<test(int)>::value , "");

   static_assert( has_no_viable_mem_fun_call_bar<test(void)>::value , "");

   return 0;
}

Đây là mã, được viết bằng c ++ 11, tuy nhiên, bạn có thể dễ dàng chuyển nó (với các điều chỉnh nhỏ) sang không phải c ++ 11 có phần mở rộng typeof (ví dụ gcc). Bạn có thể thay thế macro HAS_MEM bằng macro của riêng bạn.

#pragma once

#if __cplusplus >= 201103

#include <utility>
#include <type_traits>

#define HAS_MEM(mem)                                                                                     \
                                                                                                     \
template < typename T >                                                                               \
struct has_mem_##mem                                                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  struct ambiguate_seed { char mem; };                                                               \
  template < typename U > struct ambiguate : U, ambiguate_seed {};                                   \
                                                                                                     \
  template < typename U, typename = decltype(&U::mem) > static constexpr no  test(int);              \
  template < typename                                 > static constexpr yes test(...);              \
                                                                                                     \
  static bool constexpr value = std::is_same<decltype(test< ambiguate<T> >(0)),yes>::value ;         \
  typedef std::integral_constant<bool,value>    type;                                                \
};


#define HAS_MEM_FUN_CALL(memfun)                                                                         \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_valid_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_valid_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  struct yes {};                                                                                     \
  struct no  {};                                                                                     \
                                                                                                     \
  template < typename U, bool = has_mem_##memfun<U>::value >                                         \
  struct impl                                                                                        \
  {                                                                                                  \
     template < typename V, typename = decltype(std::declval<V>().memfun(std::declval<Args>()...)) > \
     struct test_result { using type = yes; };                                                       \
                                                                                                     \
     template < typename V > static constexpr typename test_result<V>::type test(int);               \
     template < typename   > static constexpr                            no test(...);               \
                                                                                                     \
     static constexpr bool value = std::is_same<decltype(test<U>(0)),yes>::value;                    \
     using type = std::integral_constant<bool, value>;                                               \
  };                                                                                                 \
                                                                                                     \
  template < typename U >                                                                            \
  struct impl<U,false> : std::false_type {};                                                         \
                                                                                                     \
  static constexpr bool value = impl<T>::value;                                                      \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_ambiguous_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_ambiguous_mem_fun_call_##memfun< T(Args...) >                                              \
{                                                                                                     \
  struct ambiguate_seed { void memfun(...); };                                                       \
                                                                                                     \
  template < class U, bool = has_mem_##memfun<U>::value >                                            \
  struct ambiguate : U, ambiguate_seed                                                               \
  {                                                                                                  \
    using ambiguate_seed::memfun;                                                                    \
    using U::memfun;                                                                                 \
  };                                                                                                 \
                                                                                                     \
  template < class U >                                                                               \
  struct ambiguate<U,false> : ambiguate_seed {};                                                     \
                                                                                                     \
  static constexpr bool value = not has_valid_mem_fun_call_##memfun< ambiguate<T>(Args...) >::value; \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_viable_mem_fun_call_##memfun;                                                              \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_viable_mem_fun_call_##memfun< T(Args...) >                                                 \
{                                                                                                     \
  static constexpr bool value = has_valid_mem_fun_call_##memfun<T(Args...)>::value                   \
                             or has_ambiguous_mem_fun_call_##memfun<T(Args...)>::value;              \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct has_no_viable_mem_fun_call_##memfun;                                                           \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct has_no_viable_mem_fun_call_##memfun < T(Args...) >                                             \
{                                                                                                     \
  static constexpr bool value = not has_viable_mem_fun_call_##memfun<T(Args...)>::value;             \
  using type = std::integral_constant<bool, value>;                                                  \
};                                                                                                    \
                                                                                                     \
template < typename Signature >                                                                       \
struct result_of_mem_fun_call_##memfun;                                                               \
                                                                                                     \
template < typename T, typename... Args >                                                             \
struct result_of_mem_fun_call_##memfun< T(Args...) >                                                  \
{                                                                                                     \
  using type = decltype(std::declval<T>().memfun(std::declval<Args>()...));                          \
};

#endif


1

Bạn có thể bỏ qua tất cả các lập trình meta trong C ++ 14, và chỉ cần viết này sử dụng fit::conditionaltừ Fit thư viện:

template<class T>
std::string optionalToString(T* x)
{
    return fit::conditional(
        [](auto* obj) -> decltype(obj->toString()) { return obj->toString(); },
        [](auto*) { return "toString not defined"; }
    )(x);
}

Bạn cũng có thể tạo chức năng trực tiếp từ lambdas:

FIT_STATIC_LAMBDA_FUNCTION(optionalToString) = fit::conditional(
    [](auto* obj) -> decltype(obj->toString(), std::string()) { return obj->toString(); },
    [](auto*) -> std::string { return "toString not defined"; }
);

Tuy nhiên, nếu bạn đang sử dụng trình biên dịch không hỗ trợ lambdas chung, bạn sẽ phải viết các đối tượng hàm riêng biệt:

struct withToString
{
    template<class T>
    auto operator()(T* obj) const -> decltype(obj->toString(), std::string())
    {
        return obj->toString();
    }
};

struct withoutToString
{
    template<class T>
    std::string operator()(T*) const
    {
        return "toString not defined";
    }
};

FIT_STATIC_FUNCTION(optionalToString) = fit::conditional(
    withToString(),
    withoutToString()
);

1
Làm thế nào dễ dàng để viết này để không phải phụ thuộc fithoặc bất kỳ thư viện nào ngoài tiêu chuẩn?
einpoklum

1

Với C ++ 20, bạn có thể viết như sau:

template<typename T>
concept has_toString = requires(const T& t) {
    t.toString();
};

template<typename T>
std::string optionalToString(const T& obj)
{
    if constexpr (has_toString<T>)
        return obj.toString();
    else
        return "toString not defined";
}

0

Dưới đây là một ví dụ về mã làm việc.

template<typename T>
using toStringFn = decltype(std::declval<const T>().toString());

template <class T, toStringFn<T>* = nullptr>
std::string optionalToString(const T* obj, int)
{
    return obj->toString();
}

template <class T>
std::string optionalToString(const T* obj, long)
{
    return "toString not defined";
}

int main()
{
    A* a;
    B* b;

    std::cout << optionalToString(a, 0) << std::endl; // This is A
    std::cout << optionalToString(b, 0) << std::endl; // toString not defined
}

toStringFn<T>* = nullptrsẽ cho phép hàm lấy thêm intđối số có mức độ ưu tiên so với hàm sẽ longđược gọi khi có 0.

Bạn có thể sử dụng cùng một nguyên tắc cho các hàm trả về truenếu hàm được thực thi.

template <typename T>
constexpr bool toStringExists(long)
{
    return false;
}

template <typename T, toStringFn<T>* = nullptr>
constexpr bool toStringExists(int)
{
    return true;
}


int main()
{
    A* a;
    B* b;

    std::cout << toStringExists<A>(0) << std::endl; // true
    std::cout << toStringExists<B>(0) << std::endl; // false
}

0

Tôi đã có một vấn đề tương tự:

Một lớp mẫu có thể được bắt nguồn từ một vài lớp cơ sở, một số lớp có một thành viên nhất định và những lớp khác thì không.

Tôi đã giải quyết nó tương tự như câu trả lời "typeof" (của Nicola Bonelli), nhưng với dectype để nó biên dịch và chạy chính xác trên MSVS:

#include <iostream>
#include <string>

struct Generic {};    
struct HasMember 
{
  HasMember() : _a(1) {};
  int _a;
};    

// SFINAE test
template <typename T>
class S : public T
{
public:
  std::string foo (std::string b)
  {
    return foo2<T>(b,0);
  }

protected:
  template <typename T> std::string foo2 (std::string b, decltype (T::_a))
  {
    return b + std::to_string(T::_a);
  }
  template <typename T> std::string foo2 (std::string b, ...)
  {
    return b + "No";
  }
};

int main(int argc, char *argv[])
{
  S<HasMember> d1;
  S<Generic> d2;

  std::cout << d1.foo("HasMember: ") << std::endl;
  std::cout << d2.foo("Generic: ") << std::endl;
  return 0;
}

0

Một cách nữa để làm điều đó trong C ++ 17 (lấy cảm hứng từ boost: hana).

Viết nó một lần và sử dụng nhiều lần. Nó không yêu cầu has_something<T>các lớp tính trạng loại.

#include <type_traits>

template<typename T, typename F>
constexpr auto is_valid(F&& f) -> decltype(f(std::declval<T>()), true) { return true; }

template<typename>
constexpr bool is_valid(...) { return false; }

#define IS_VALID(T, EXPR) is_valid<T>( [](auto&& obj)->decltype(obj.EXPR){} )

Thí dụ

#include <iostream>

struct Example {
    int Foo;
    void Bar() {}
    std::string toString() { return "Hello from toString()!"; }
};

struct Example2 {
    int X;
};

template<class T>
std::string optionalToString(T* obj)
{
    if constexpr(IS_VALID(T, toString()))
        return obj->toString();
    else
        return "toString not defined";
}

int main() {
    static_assert(IS_VALID(Example, Foo));
    static_assert(IS_VALID(Example, Bar()));
    static_assert(!IS_VALID(Example, ZFoo));
    static_assert(!IS_VALID(Example, ZBar()));

    Example e1;
    Example2 e2;

    std::cout << "e1: " << optionalToString(&e1) << "\n";
    std::cout << "e1: " << optionalToString(&e2) << "\n";
}

-1
template<class T>
auto optionalToString(T* obj)
->decltype( obj->toString(), std::string() )
{
     return obj->toString();
}

template<class T>
auto optionalToString(T* obj)
->decltype( std::string() )
{
     throw "Error!";
}

6
"Chúng tôi không cần mô tả câu trả lời" ... vui lòng thêm một số mô tả thông tin vào câu trả lời của bạn để cải thiện nó. Cảm ơn.
CóThatIsMyName
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.