Templated ตรวจสอบการมีอยู่ของฟังก์ชันสมาชิกคลาสหรือไม่


498

เป็นไปได้ไหมที่จะเขียนเทมเพลตที่เปลี่ยนลักษณะการทำงานขึ้นอยู่กับว่าฟังก์ชั่นสมาชิกบางตัวมีการกำหนดไว้ในคลาสหรือไม่?

นี่คือตัวอย่างง่ายๆของสิ่งที่ฉันต้องการเขียน:

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

ดังนั้นหากclass TมีการtoString()กำหนดไว้แล้วมันจะใช้มัน มิฉะนั้นจะไม่ ส่วนมหัศจรรย์ที่ฉันไม่รู้ว่าต้องทำอย่างไรคือส่วน "FUNCTION_EXISTS"


6
แน่นอนว่าไม่ต้องบอกว่าเทมเพลตคำตอบด้านล่างนี้ใช้ได้กับข้อมูลเวลารวบรวมเท่านั้นเช่น T ต้องมี toString หากคุณผ่านใน subclass ของ T ที่ไม่กำหนด toString แต่ T ไม่ได้คุณจะบอก toString ไม่ได้กำหนดไว้
อลิซเพอร์เซลล์

คำตอบ:


319

ได้ด้วย SFINAE คุณสามารถตรวจสอบว่าคลาสที่ระบุให้วิธีการบางอย่างได้หรือไม่ นี่คือรหัสการทำงาน:

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

ฉันเพิ่งทดสอบกับ Linux และ gcc 4.1 / 4.3 ฉันไม่รู้ว่ามันสามารถพกพาไปยังแพลตฟอร์มอื่นที่ใช้คอมไพเลอร์ต่างกันได้หรือไม่


18
แม้ว่าฉันจะใช้สิ่งต่อไปนี้สำหรับ 'หนึ่ง' และ 'สอง': typedef char Small; คลาส Big {char dummy [2];} เพื่อให้แน่ใจว่าไม่มีความคลุมเครือเกี่ยวกับขนาดตัวแปรที่ขึ้นกับแพลตฟอร์ม
user23167

6
ฉันสงสัยว่ามันมีอยู่บนพื้นโลกที่มีขนาด (char) == sizeof (ยาว)
Nicola Bonelli

17
ฉันไม่แน่ใจทั้งหมด แต่ฉันไม่คิดว่ามันเป็นแบบพกพา typeof เป็นส่วนขยาย GCC ซึ่งจะไม่ทำงานกับคอมไพเลอร์อื่น ๆ
Leon Timmermans

56
ไม่จำเป็นต้องใช้ typeof - char [sizeof (& C :: helloworld)] ก็ใช้ได้เช่นกัน และเพื่อหลีกเลี่ยง sizeof (long) == sizeof (char) ให้ใช้ struct {char [2]}; จะต้องมีขนาด> = 2
MSalters

57
เล็กน้อย แต่ใช้เวลาสักครู่กว่าจะคิดออก: แทนที่typeofด้วยdecltypeเมื่อใช้C ++ 0xเช่นผ่าน -std = c ++ 0x
hrr

264

คำถามนี้เก่า แต่ด้วย C ++ 11 เรามีวิธีใหม่ในการตรวจสอบการมีอยู่ของฟังก์ชัน (หรือการมีอยู่ของสมาชิกที่ไม่ใช่ประเภทใด ๆ จริงๆ) โดยอาศัย SFINAE อีกครั้ง:

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

ตอนนี้เข้าสู่คำอธิบายบางอย่าง อย่างแรกเลยฉันใช้expression SFINAEเพื่อแยกserialize(_imp)ฟังก์ชั่นออกจากความละเอียดเกินพิกัดหากนิพจน์แรกข้างในdecltypeไม่ถูกต้อง (aka ไม่มีฟังก์ชั่น)

void()จะใช้เพื่อให้พิมพ์กลับของฟังก์ชันเหล่านั้นทั้งหมดvoidจะใช้เพื่อให้พิมพ์กลับของฟังก์ชันเหล่านั้นทั้งหมด

0อาร์กิวเมนต์จะใช้ในการชอบos << objเกินถ้าทั้งสองมีอยู่ (ตัวอักษร0เป็นชนิดintและเป็นเช่นเกินพิกัดแรกคือการแข่งขันที่ดีกว่า)


ตอนนี้คุณอาจต้องการคุณลักษณะเพื่อตรวจสอบว่ามีฟังก์ชันอยู่หรือไม่ โชคดีที่เขียนได้ง่าย อย่างไรก็ตามโปรดทราบว่าคุณต้องเขียนคุณสมบัติด้วยตัวเองสำหรับชื่อฟังก์ชั่นต่าง ๆ ที่คุณอาจต้องการ

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

ตัวอย่างสด

และเพื่ออธิบาย ครั้งแรกเป็นประเภทผู้ช่วยและมันเป็นพื้นจำนวนเงินเช่นเดียวกับการเขียนsfinae_true decltype(void(std::declval<T>().stream(a0)), std::true_type{})ข้อดีก็คือมันสั้นกว่า
ถัดไปการstruct has_stream : decltype(...)สืบทอดจากอย่างใดอย่างหนึ่งstd::true_typeหรือstd::false_typeในท้ายที่สุดขึ้นอยู่กับว่าการdecltypeเช็คอินtest_streamล้มเหลวหรือไม่
สุดท้ายstd::declvalให้ "คุณค่า" ของสิ่งที่คุณส่งผ่านโดยที่คุณไม่จำเป็นต้องรู้ว่าคุณจะสร้างมันได้อย่างไร โปรดทราบว่านี้เป็นไปได้เฉพาะภายในบริบท unevaluated เช่นdecltype, sizeofและอื่น ๆ


โปรดทราบว่าdecltypeไม่จำเป็นต้องมีเนื่องจากsizeof(และบริบทที่ไม่ได้ประเมินทั้งหมด) ได้รับการปรับปรุงดังกล่าว มันเป็นเพียงที่decltypeส่งมอบประเภทและเป็นเช่นนั้นเพียงทำความสะอาด นี่เป็นsizeofเวอร์ชั่นของหนึ่งในโอเวอร์โหลด:

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

intและlongพารามิเตอร์ยังคงมีเหตุผลเดียวกัน ตัวชี้อาร์เรย์ใช้เพื่อจัดเตรียมบริบทที่sizeofสามารถใช้ได้


4
ข้อได้เปรียบของdecltypeover sizeofคือการไม่แนะนำชั่วคราวโดยกฎที่สร้างขึ้นเป็นพิเศษสำหรับการเรียกใช้ฟังก์ชัน (ดังนั้นคุณไม่จำเป็นต้องมีสิทธิ์การเข้าถึงตัวทำลายชนิดการส่งคืนและจะไม่ทำให้เกิดการสร้างอินสแตนซ์โดยนัย แม่แบบคลาสอินสแตนซ์คลาส)
Johannes Schaub - litb

5
Microsoft ยังไม่ได้ติดตั้ง Expression SFINAE ในคอมไพเลอร์ C ++ แค่คิดว่าฉันอาจช่วยคนบางคนประหยัดเวลาเพราะฉันสับสนว่าทำไมมันถึงไม่เหมาะกับฉัน วิธีแก้ปัญหาที่ดีไม่สามารถรอที่จะใช้ใน Visual Studio ได้!
Jonathan

3
ลิงก์ตัวอย่างแรกของคุณเสีย
NathanOliver

1
จะต้องมีการกล่าวว่าstatic_assert(has_stream<X, char>() == true, "fail X");จะรวบรวมและไม่ยืนยันเพราะถ่านสามารถแปลงเป็น int ดังนั้นหากพฤติกรรมนั้นไม่ต้องการและต้องการให้อาร์กิวเมนต์ทุกประเภทตรงกับที่ฉันไม่รู้ว่าจะทำได้อย่างไร
Gabriel

4
หากคุณงงเหมือนอย่างที่ฉันเคยเป็นทั้งสองข้อโต้เถียงในการปฏิเสธประเภท: decltype ใช้เวลาเดียวเท่านั้น เครื่องหมายจุลภาคเป็นตัวดำเนินการที่นี่ ดูstackoverflow.com/questions/16044514/…
André

159

C ++ อนุญาตให้SFINAEใช้งานได้ (โปรดสังเกตว่าด้วยคุณสมบัติ C ++ 11 ซึ่งทำได้ง่ายกว่าเพราะสนับสนุน SFINAE ที่ขยายเพิ่มในการแสดงออกโดยพลการเกือบ - ด้านล่างนี้ถูกสร้างขึ้นเพื่อทำงานกับคอมไพเลอร์ C ++ 03 ทั่วไป):

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

เทมเพลตและมาโครด้านบนจะพยายามสร้างแม่แบบยกตัวอย่างให้เป็นประเภทตัวชี้ฟังก์ชันสมาชิกและตัวชี้ฟังก์ชันสมาชิกจริง ถ้าชนิดไม่พอดี SFINAE ทำให้เทมเพลตถูกละเว้น การใช้งานเช่นนี้:

HAS_MEM_FUNC(toString, has_to_string);

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

แต่โปรดทราบว่าคุณไม่สามารถเรียกtoStringฟังก์ชั่นนั้นในกรณีนั้นได้หากสาขา เนื่องจากคอมไพเลอร์จะตรวจสอบความถูกต้องในทั้งสองสาขาซึ่งจะล้มเหลวในกรณีที่ฟังก์ชันไม่มีอยู่ วิธีหนึ่งคือการใช้ SFINAE อีกครั้ง (enable_if สามารถได้รับจากการเพิ่มด้วย):

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

ขอให้สนุกกับการใช้มัน ข้อได้เปรียบของมันคือมันยังใช้งานได้สำหรับฟังก์ชั่นสมาชิกที่โอเวอร์โหลดและฟังก์ชั่นสมาชิกสมาชิก const (โปรดจำไว้ว่าใช้ std::string(T::*)() constเป็นประเภทตัวชี้ฟังก์ชั่นสมาชิกแล้ว!)


7
ฉันชอบวิธีtype_checkใช้เพื่อให้แน่ใจว่าลายเซ็นเห็นด้วยอย่างแน่นอน มีวิธีที่จะทำให้มันจะตรงกับวิธีการใด ๆ ที่สามารถเรียกว่าในลักษณะที่วิธีการที่มีลายเซ็นSignสามารถเรียก? (เช่น if Sign= std::string(T::*)()อนุญาตให้std::string T::toString(int default = 42, ...)จับคู่)
j_random_hacker

5
ฉันแค่คิดอะไรบางอย่างเกี่ยวกับสิ่งนี้ที่ไม่ได้ชัดเจนสำหรับฉันดังนั้นในกรณีที่มันช่วยคนอื่น: chk ไม่จำเป็นและไม่จำเป็นต้องถูกกำหนด! ตัวดำเนินการ sizeof กำหนดขนาดของเอาต์พุตของ chk โดยไม่จำเป็นต้องเรียกใช้ chk
SCFrench

3
@ deek0146: ใช่Tจะต้องไม่ใช่ประเภทดึกดำบรรพ์เนื่องจากการประกาศตัวชี้ไปยังวิธีการของ T ไม่อยู่ภายใต้ SFINAE และจะเกิดข้อผิดพลาดสำหรับการที่ไม่ใช่คลาส IMO ทางออกที่ง่ายที่สุดคือการรวมกับการis_classตรวจสอบจาก การส่งเสริม
Jan Hudec

2
ฉันจะทำงานนี้ได้อย่างไรถ้าฉันtoStringเป็นฟังก์ชัน templated
แฟรงค์

4
นี่คือ (หรืออะไรที่เทียบเท่า) ใน Boost หรือไม่
Dan Nissenbaum

89

C ++ 20 - requiresนิพจน์

ด้วย C ++ 20 มีแนวคิดและเครื่องมือหลากหลายเช่นrequiresนิพจน์ซึ่งเป็นวิธีที่มีอยู่แล้วภายในเพื่อตรวจสอบการมีอยู่ของฟังก์ชัน คุณสามารถเขียนoptionalToStringฟังก์ชันของคุณใหม่ได้ดังนี้

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 - ชุดเครื่องมือตรวจจับ

N4502เสนอชุดเครื่องมือตรวจจับเพื่อรวมไว้ในไลบรารีมาตรฐาน C ++ 17 ซึ่งในที่สุดก็ทำให้เป็นพื้นฐานของไลบรารี TS v2 เป็นไปได้ว่าจะไม่เข้ามาตรฐานเพราะเคยมีการrequiresแสดงออกมานับ แต่มันยังแก้ปัญหาได้อย่างสง่างาม ชุดเครื่องมือแนะนำ metafunctions บางอย่างรวมถึงstd::is_detectedที่สามารถใช้เพื่อเขียนประเภทหรือฟังก์ชั่นการตรวจสอบการทำงาน metafunctions ด้านบนของมัน นี่คือวิธีการใช้งาน:

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

โปรดทราบว่าตัวอย่างข้างต้นยังไม่ได้ทดสอบ ชุดเครื่องมือตรวจหายังไม่พร้อมใช้งานในไลบรารีมาตรฐาน แต่ข้อเสนอนี้มีการใช้งานอย่างเต็มรูปแบบที่คุณสามารถคัดลอกได้อย่างง่ายดายหากคุณต้องการ มันเล่นได้ดีกับฟีเจอร์ 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 เห็นได้ชัดว่าสร้างตามตัวอย่างที่เฉพาะเจาะจงนี้และมอบโซลูชันสำหรับ C ++ 14 ในเอกสารประกอบดังนั้นฉันจะเสนอราคาโดยตรง:

[... ] Hana มีis_validฟังก์ชั่นที่สามารถใช้ร่วมกับ lambdas ทั่วไป C ++ 14 เพื่อรับการใช้งานที่สะอาดกว่าในสิ่งเดียวกัน:

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

สิ่งนี้ทำให้เรามีฟังก์ชั่นวัตถุhas_toStringที่ส่งกลับว่าการแสดงออกที่กำหนดนั้นถูกต้องในการโต้แย้งเราผ่าน ผลลัพธ์ถูกส่งคืนเป็นแบบIntegralConstantดังนั้น constexpr-ness จึงไม่ใช่ปัญหาที่นี่เนื่องจากผลลัพธ์ของฟังก์ชันถูกแสดงเป็นชนิดอย่างไรก็ตาม ตอนนี้นอกเหนือไปจาก verbose น้อย (นั่นคือหนึ่งซับ!) ความตั้งใจที่ชัดเจนมากขึ้น ผลประโยชน์อื่น ๆ คือความจริงที่has_toStringสามารถส่งผ่านไปยังอัลกอริธึมการสั่งซื้อที่สูงขึ้นและยังสามารถกำหนดได้ที่ขอบเขตการทำงานดังนั้นจึงไม่จำเป็นต้องทำให้เกิดมลภาวะขอบเขตเนมสเปซ

Boost.TTI

ชุดเครื่องมือที่ใช้สำนวนอื่น ๆ เพื่อทำการตรวจสอบ - แม้ว่าจะมีความสง่างามน้อยกว่าก็ตามคือBoost.TTIซึ่งได้รับการแนะนำใน Boost 1.54.0 BOOST_TTI_HAS_MEMBER_FUNCTIONตัวอย่างเช่นคุณจะต้องใช้แมโคร นี่คือวิธีการใช้งาน:

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

จากนั้นคุณสามารถใช้boolเพื่อสร้างการตรวจสอบ SFINAE

คำอธิบาย

แมโครBOOST_TTI_HAS_MEMBER_FUNCTIONสร้าง metafunction has_member_function_toStringซึ่งใช้ชนิดที่เลือกเป็นพารามิเตอร์แม่แบบตัวแรก พารามิเตอร์เทมเพลตที่สองสอดคล้องกับชนิดส่งคืนของฟังก์ชันสมาชิกและพารามิเตอร์ต่อไปนี้สอดคล้องกับชนิดของพารามิเตอร์ของฟังก์ชัน สมาชิกvalueประกอบด้วยtrueถ้าคลาสTstd::string toString()มีฟังก์ชั่นสมาชิก

หรือhas_member_function_toStringอาจใช้ตัวชี้ฟังก์ชันสมาชิกเป็นพารามิเตอร์เทมเพลต ดังนั้นจึงเป็นไปได้ที่จะเข้ามาแทนที่โดยhas_member_function_toString<T, std::string>::valuehas_member_function_toString<std::string T::* ()>::value


1
กระชับกว่า 03
ZFY

@ZFY ฉันคิดว่า Boost.TTI ทำงานร่วมกับ C ++ 03 เช่นกัน แต่มันก็เป็นทางออกที่ยอดเยี่ยมที่สุด
Morwenn

โซลูชัน C ++ 20 ใช้ได้จริงหรือไม่ ฉันต้องการมัน - แต่มันถูกปฏิเสธโดย g ++ และ msvc - ยอมรับโดยเสียงดังกราวเท่านั้น
Bernd Baumanns

ที่ cppreference คุณสามารถอ่านได้: ถ้า require-expression มีประเภทหรือนิพจน์ที่ไม่ถูกต้องตามข้อกำหนดของมันและมันไม่ปรากฏภายในการประกาศขององค์กรที่มี templated โปรแกรมนั้นจะเกิดขึ้นไม่ดี
Bernd Baumanns

@BerndBaumanns จริงเหรอ? ฉันได้มันไปทำงานกับลำตัว GCC: godbolt.org/z/CBwZdEบางทีคุณพูดถูกฉันแค่ตรวจสอบว่ามันทำงานได้ แต่ไม่ได้ตรวจสอบว่ามันถูกกฎหมายตามถ้อยคำมาตรฐานหรือไม่
Morwenn

56

แม้ว่าคำถามนี้จะมีอายุสองปี แต่ฉันก็กล้าที่จะเพิ่มคำตอบของฉัน หวังว่ามันจะทำให้การแก้ปัญหาที่ผ่านมาดีเยี่ยมอย่างไม่ต้องสงสัย ฉันได้รับคำตอบที่เป็นประโยชน์มากของ Nicola Bonelli และ Johannes Schaub และรวมเข้ากับโซลูชันที่ IMHO อ่านได้ชัดเจนขึ้นและไม่ต้องใช้typeofส่วนขยาย:

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

ฉันตรวจสอบด้วย gcc 4.1.2 เครดิตส่วนใหญ่ไปที่ Nicola Bonelli และ Johannes Schaub ดังนั้นให้พวกเขาโหวตถ้าคำตอบของฉันช่วยคุณ :)


1
เพียงแค่สงสัยว่าสิ่งนี้จะทำสิ่งใดก็ตามที่โซลูชันของ Konrad Rudolph ด้านล่างไม่ได้ทำหรือไม่?
Alastair Irvine

3
@AlastairIrvine โซลูชันนี้ซ่อนตรรกะทั้งหมดไว้ภายใน Konrad's วางภาระบางอย่างให้กับผู้ใช้ แม้ว่าในระยะสั้นและอ่านได้มากขึ้น, toStringการแก้ปัญหาคอนราดต้องมีความเชี่ยวชาญแม่แบบแยกต่างหากสำหรับแต่ละชั้นเรียนที่มี หากคุณเขียนไลบรารี่ทั่วไปที่ต้องการทำงานกับคลาสใด ๆ ที่นั่น (คิดว่าเป็นอะไรที่พิเศษ) ให้ผู้ใช้กำหนดความเชี่ยวชาญเพิ่มเติมของเทมเพลตที่คลุมเครือบางอย่างอาจไม่สามารถยอมรับได้ บางครั้งก็ควรเขียนรหัสที่ซับซ้อนมาก ๆ เพื่อให้ส่วนต่อประสานสาธารณะนั้นเรียบง่ายที่สุดเท่าที่จะทำได้
FireAphis

30

ทางออกที่ง่ายสำหรับ C ++ 11:

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

อัปเดต 3 ปีต่อมา: (และนี่ยังไม่ได้ทดสอบ) เพื่อทดสอบการมีอยู่ฉันคิดว่าสิ่งนี้จะได้ผล:

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
นี่คือเรียบง่ายและสง่างาม แต่การพูดอย่างเคร่งครัดไม่ตอบคำถามของ OP: คุณไม่ได้เปิดใช้งานผู้โทรเพื่อตรวจสอบการมีอยู่ของฟังก์ชันคุณให้มันเสมอ แต่ก็ดีอยู่ดี
Adrian W

@AdrianW จุดดี ฉันอัพเดตคำตอบแล้ว ฉันยังไม่ได้ทดสอบ
Aaron McDaid

ในกรณีที่ช่วยคนอื่นฉันไม่สามารถทำงานนี้ได้โดยไม่ต้องtemplate<typename>ก่อนที่จะมีตัวแปรมากเกินไป: มันไม่ได้รับการพิจารณาเพื่อแก้ไขปัญหา
Laboratorio Cobotica

นี่เป็น C ++ 11 ที่ไม่ถูกต้องอีกครั้ง
ปีเตอร์

29

นี่คือสิ่งที่มีลักษณะประเภทสำหรับ น่าเสียดายที่ต้องกำหนดด้วยตนเอง ในกรณีของคุณลองนึกภาพสิ่งต่อไปนี้:

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
คุณควรเลือก enum สำหรับลักษณะแทนค่าคงที่แบบคงที่: "สมาชิกคงที่แบบคงที่คือ lvalues ​​ซึ่งบังคับให้คอมไพเลอร์สร้างอินสแตนซ์และจัดสรรคำจำกัดความสำหรับสมาชิกแบบคงที่ด้วยเหตุนี้การคำนวณจึงไม่ จำกัด เพียงการรวบรวม" บริสุทธิ์ "ผลกระทบ"
Özgür

5
"ค่าการแจงนับไม่ได้เป็น lvalues ​​(นั่นคือพวกเขาไม่มีที่อยู่) ดังนั้นเมื่อคุณส่งพวกเขา" โดยการอ้างอิง "ไม่มีการใช้หน่วยความจำคงที่มันเกือบจะเหมือนกับว่าคุณผ่านค่าที่คำนวณเป็นตัวอักษร ข้อควรพิจารณาเหล่านี้กระตุ้นให้เราใช้ค่าการแจงนับ "แม่แบบ C ++: The Complete Guide
Özgür

22
Comptrol: ไม่ข้อความอ้างอิงที่ใช้ไม่ได้ที่นี่เนื่องจากค่าคงที่ประเภทจำนวนเต็มเป็นกรณีพิเศษ! พวกมันมีพฤติกรรมเหมือน Enum ตรงนี้และเป็นวิธีที่ต้องการ การแฮ็ก Enum แบบเก่านั้นมีความจำเป็นเฉพาะกับคอมไพเลอร์ที่ไม่ได้มาตรฐาน C ++
Konrad Rudolph

3
@Roger Pate: ไม่มาก “ ใช้ในโปรแกรม” ที่นี่มีความหมายเหมือนกันกับ“ อ้างอิง” การอ่านข้อความนี้ที่แพร่หลายและสิ่งที่นำมาใช้โดยคอมไพเลอร์ C ++ ที่ทันสมัยทั้งหมดคือคุณสามารถรับค่าของค่าคงที่แบบคงที่โดยไม่จำเป็นต้องประกาศ (ประโยคก่อนหน้าบอกว่า: "... สมาชิกสามารถปรากฏในนิพจน์ค่าคงที่หนึ่ง ...”) คุณจะต้องกำหนดมันหากคุณใช้ที่อยู่ของมัน (อย่างชัดเจนผ่าน&T::xหรือโดยนัยโดยการผูกไว้กับการอ้างอิง)
Konrad Rudolph


25

คำถามนี้มีรายการคำตอบยาว ๆ อยู่แล้ว แต่ฉันอยากจะเน้นความคิดเห็นจาก Morwenn: มีข้อเสนอสำหรับ C ++ 17 ที่ทำให้มันง่ายขึ้นมาก ดูรายละเอียดที่N4502แต่เป็นตัวอย่างที่มีในตัวเองให้พิจารณาสิ่งต่อไปนี้

ส่วนนี้เป็นส่วนที่คงที่ใส่ไว้ในส่วนหัว

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

จากนั้นมีส่วนตัวแปรที่คุณระบุสิ่งที่คุณกำลังมองหา (ประเภทประเภทสมาชิกฟังก์ชั่นฟังก์ชั่นสมาชิก ฯลฯ ) ในกรณีของ OP:

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

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

ตัวอย่างต่อไปนี้นำมาจากN4502แสดงโพรบที่ซับซ้อนมากขึ้น:

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

เมื่อเทียบกับการใช้งานอื่น ๆ ที่อธิบายไว้ข้างต้นอันนี้ค่อนข้างง่าย: ชุดเครื่องมือที่ลดลง ( void_tและdetect) ไม่เพียงพอสำหรับมาโครที่มีขนดก นอกจากนี้ยังมีรายงานอีกด้วย (ดูN4502 ) ว่ามีประสิทธิภาพมากกว่า (การรวบรวมเวลาและการใช้หน่วยความจำคอมไพเลอร์) ที่วัดได้มากกว่าวิธีการก่อนหน้านี้

นี่คือตัวอย่างที่มีชีวิต มันใช้งานได้ดีกับ Clang แต่น่าเสียดายที่รุ่น GCC ก่อน 5.1 ตามการตีความมาตรฐาน C ++ 11 ที่แตกต่างกันซึ่งทำให้void_tการทำงานไม่เป็นไปตามที่คาดไว้ Yakk ได้เตรียมวิธีแก้ปัญหาไว้แล้ว: ใช้คำจำกัดความต่อไปนี้ของvoid_t( void_t ในรายการพารามิเตอร์ใช้งานได้ แต่ไม่ใช่ประเภทการส่งคืน ):

#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

เป็นไปได้ไหมที่จะขยายออกไปเพื่อตรวจจับฟังก์ชันที่ไม่ใช่สมาชิก
plasmacel

แน่นอน. ดูตัวอย่างอย่างรอบคอบ: โดยพื้นฐานแล้วคุณจะต้องมีการแสดงออกและตรวจสอบว่ามันถูกต้อง ไม่มีสิ่งใดที่ต้องการให้นิพจน์นี้เกี่ยวกับการเรียกใช้ฟังก์ชันสมาชิกเท่านั้น
akim

N4502 ( open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf ) เป็นหนทางแห่งอนาคต ... ฉันกำลังมองหาวิธีตรวจจับสิ่งต่าง ๆ อย่างเป็นระเบียบและ N4502 เป็นวิธี ไป.
tlonuk

11

นี่เป็นวิธีแก้ปัญหา C ++ 11 สำหรับปัญหาทั่วไปหาก "ถ้าฉันทำ X จะรวบรวมหรือไม่"

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

ลักษณะhas_to_stringเช่นนั้นhas_to_string<T>::valueก็trueต่อเมื่อTมีวิธีการ.toStringที่สามารถเรียกใช้ได้โดยมีอาร์กิวเมนต์ 0 ตัวในบริบทนี้

ต่อไปฉันจะใช้การแจกจ่ายแท็ก:

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

ซึ่งมีแนวโน้มที่จะรักษาได้มากกว่านิพจน์ SFINAE ที่ซับซ้อน

คุณสามารถเขียนคุณลักษณะเหล่านี้ด้วยมาโครหากคุณพบว่าตัวเองทำมันมาก แต่มันค่อนข้างง่าย (แต่ละบรรทัดไม่กี่บรรทัด) ดังนั้นอาจไม่คุ้มกับมันเลย:

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

MAKE_CODE_TRAITสิ่งดังกล่าวข้างต้นไม่สามารถสร้างแมโคร Tคุณผ่านมันชื่อของลักษณะที่คุณต้องการและรหัสบางอย่างที่สามารถทดสอบชนิด ดังนั้น:

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

สร้างคลาสลักษณะข้างต้น

นอกจากเทคนิคข้างต้นเป็นส่วนหนึ่งของสิ่งที่ MS เรียกว่า "expression SFINAE" และคอมไพเลอร์ 2013 ของพวกเขาล้มเหลวอย่างหนัก

โปรดทราบว่าใน C ++ 1y ไวยากรณ์ต่อไปนี้เป็นไปได้:

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

ซึ่งเป็นสาขาที่มีเงื่อนไขในการคอมไพล์แบบอินไลน์ซึ่งใช้คุณลักษณะ C ++ มากมาย การทำเช่นนั้นอาจไม่คุ้มค่าเนื่องจากประโยชน์ (ของรหัสเป็นแบบอินไลน์) นั้นไม่คุ้มกับค่าใช้จ่าย (จากไม่มีใครเข้าใจวิธีการทำงาน) แต่การมีอยู่ของวิธีการแก้ปัญหาข้างต้นอาจเป็นที่สนใจ


สิ่งนี้จัดการกรณีส่วนตัวหรือไม่?
tower120

@ tower120 ฉันจะต้องทดสอบ: แม่แบบโต้ตอบกับส่วนตัว / สาธารณะ / ได้รับการป้องกันเป็นบิตคลุมเครือให้ฉัน มันจะไม่สำคัญว่าคุณจะเรียกใช้has_to_stringอย่างไรก็ตาม
Yakk - Adam Nevraumont

แต่คุณรู้ไหมถ้ามองจากอีกด้านหนึ่ง ... เราสามารถเข้าถึงสมาชิกที่ได้รับความคุ้มครองจากชั้นเรียนที่ได้รับ บางทีถ้าใส่ทุกอย่างในชั้นเรียนนี้และแปลงจาก structs เป็นฟังก์ชั่น constexpr ...
tower120

ที่นี่ดูcoliru.stacked-crooked.com/a/ee94d16e7c07e093นี้ฉันทำมันไม่ได้ constexpr
tower120

@ tower120 C ++ 1y ทำให้ใช้งานได้: coliru.stacked-crooked.com/a/d8cdfff24a171394
Yakk - Adam Nevraumont

10

นี่คือตัวอย่างการใช้งาน: * ความกล้าทั้งหมดนี้อยู่ไกลออกไป

ตรวจสอบสมาชิกxในชั้นเรียนที่กำหนด อาจเป็น var, func, class, union หรือ enum:

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

ตรวจสอบฟังก์ชั่นสมาชิก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;

ตรวจสอบตัวแปรสมาชิกx:

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

ตรวจสอบคลาสสมาชิกx:

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

ตรวจสอบสหภาพสมาชิกx:

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

ตรวจสอบสมาชิก enum x:

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

ตรวจสอบฟังก์ชันสมาชิกใด ๆxโดยไม่คำนึงถึงลายเซ็น:

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;

หรือ

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;

รายละเอียดและหลัก:

/*
    - 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
คุณมีความคิดว่าทำไมถ้าเราเปลี่ยนsig_check<func_sig, &T::func_name>การตรวจสอบฟังก์ชั่นฟรี: sig_check<func_sig, &func_name>มันล้มเหลวในการสร้างด้วย "ตัวบ่งชี้ที่ไม่ได้ประกาศ" ซึ่งกล่าวถึงชื่อของฟังก์ชั่นที่เราต้องการตรวจสอบ? เพราะฉันคาดหวังว่า SFINAE จะทำให้มันไม่ใช่ข้อผิดพลาดมันเป็นแบบนั้นสำหรับสมาชิกทำไมไม่ใช้ฟังก์ชั่นฟรี?
v.oddou

ฉันคิดว่ามันจะมีบางอย่างเกี่ยวกับข้อเท็จจริงที่ว่าฟังก์ชั่นฟรีไม่ได้เป็นคลาสหรือ struct เทคนิคการลดการแสดงตนของสมาชิกมุ่งเน้นไปที่กลไกการรับมรดกใน C ++ บังคับให้มีความกำกวมระหว่างคลาส stub ที่มีอยู่เพื่อจุดประสงค์ในการโฮสต์สมาชิกที่คุณกำลังตรวจสอบกับคลาสที่คุณกำลังตรวจสอบกับสมาชิกเท่านั้น ค่ะนั่นเป็นคำถามที่น่าสนใจ แต่ไม่ได้คิดเกี่ยวกับมัน คุณอาจตรวจสอบเทคนิคการตรวจสอบสมาชิก C ++ 11/14 อื่น ๆ ฉันได้เห็นบางสิ่งที่ฉลาดในมาตรฐานใหม่
Brett Rossier

ขอบคุณสำหรับคำตอบของคุณฉันคิดว่าฉันอาจต้องตรวจสอบเพิ่มเติมในเชิงลึกที่ intel คุณให้เกี่ยวกับการสืบทอดเพราะจนถึงขณะนี้ฉันไม่เห็นความสัมพันธ์ระหว่างเพียงอาศัย SFINAE เพื่อให้การแสดงออกที่ไม่ถูกต้องแสดงการเข้าถึง สมาชิกในพารามิเตอร์ประเภทเทมเพลตและการสืบทอดหลายรายการ แต่ฉันเชื่ออย่างสมบูรณ์ว่าใน C ++ แม้แต่แนวคิดที่อยู่ห่างไกลก็อาจตกเลือดกัน ตอนนี้สำหรับฟังก์ชั่นฟรีคำถามนี้น่าสนใจ: stackoverflow.com/questions/26744589คำตอบ TC ดูเหมือนจะใช้เคล็ดลับในการประกาศดัมมี่เพื่อหลีกเลี่ยง "ตัวบ่งชี้ที่ไม่ได้ประกาศ"
v.oddou

8

ฉันเขียนคำตอบนี้ในหัวข้ออื่นที่ (ไม่เหมือนคำตอบด้านบน) ตรวจสอบการทำงานของสมาชิกที่สืบทอดมา:

SFINAE เพื่อตรวจสอบฟังก์ชั่นสมาชิกที่สืบทอด

ต่อไปนี้เป็นตัวอย่างบางส่วนจากโซลูชันดังกล่าว:

example1:

เรากำลังตรวจสอบสมาชิกที่มีลายเซ็นต่อไปนี้: 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));
};

โปรดสังเกตว่ามันจะตรวจสอบความคงตัวของวิธีการและทำงานกับชนิดดั้งเดิมเช่นกัน (ฉันหมายถึงhas_const_begin<int>::valueเป็นเท็จและไม่ทำให้เกิดข้อผิดพลาดในการคอมไพล์)

ตัวอย่างที่ 2

ตอนนี้เรากำลังมองหาลายเซ็น: 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));
};

โปรดสังเกตว่า MyClass ไม่จำเป็นต้องเป็นค่าเริ่มต้นที่สามารถสร้างได้หรือเพื่อให้เป็นไปตามแนวคิดพิเศษใด ๆ เทคนิคนี้ใช้ได้กับสมาชิกเทมเพลตเช่นกัน

ฉันกำลังรอความคิดเห็นเกี่ยวกับเรื่องนี้อย่างกระตือรือร้น


7

ตอนนี้เป็นสิ่งที่ดีปริศนาเล็ก ๆ ที่ - คำถามยอดเยี่ยม!

นี่เป็นทางเลือกในการแก้ปัญหาของนิโคลา Bonelliที่ไม่พึ่งพาไม่ได้มาตรฐานtypeofผู้ประกอบการ

น่าเสียดายที่มันใช้งานไม่ได้กับ GCC (MinGW) 3.4.5 หรือ Digital Mars 8.42n แต่ทำงานได้กับ MSVC ทุกรุ่น (รวมถึง VC6) และ Comeau C ++

บล็อกความคิดเห็นที่ยาวขึ้นมีรายละเอียดเกี่ยวกับวิธีการทำงาน (หรือควรทำงาน) อย่างที่มันบอกว่าฉันไม่แน่ใจว่าพฤติกรรมแบบใดที่เป็นไปตามมาตรฐาน - ฉันยินดีรับฟังความเห็นเกี่ยวกับเรื่องนั้น


อัพเดท - 7 พฤศจิกายน 2551:

ดูเหมือนว่าในขณะที่รหัสนี้ถูกต้อง syntactically พฤติกรรมที่แสดง MSVC และ Comeau C ++ ไม่เป็นไปตามมาตรฐาน (ขอบคุณLeon Timmermansและlitbสำหรับชี้ให้ฉันในทิศทางที่ถูกต้อง) มาตรฐาน C ++ 03 กล่าวดังต่อไปนี้:

14.6.2 ชื่อที่ขึ้นอยู่กับ [temp.dep]

วรรค 3

ในคำจำกัดความของแม่แบบชั้นเรียนหรือสมาชิกของแม่แบบชั้นเรียนหากชั้นฐานของแม่แบบชั้นเรียนขึ้นอยู่กับแม่แบบพารามิเตอร์ขอบเขตระดับชั้นฐานจะไม่ตรวจสอบในระหว่างการค้นหาชื่ออย่างไม่มีเงื่อนไขที่จุดของความหมายของชั้นเรียน เทมเพลตหรือสมาชิกหรือในระหว่างการสร้างอินสแตนซ์ของเทมเพลตหรือสมาชิกคลาส

ดังนั้นดูเหมือนว่าเมื่อ MSVC หรือ Comeau พิจารณาtoString()ฟังก์ชันสมาชิกของTการทำการค้นหาชื่อที่ไซต์การโทรdoToString()เมื่อแม่แบบถูกสร้างอินสแตนซ์นั่นไม่ถูกต้อง

พฤติกรรมของ GCC และ Digital Mars ดูเหมือนจะถูกต้อง - ในทั้งสองกรณีtoString()ฟังก์ชันที่ไม่ใช่สมาชิกจะถูกผูกไว้กับการโทร

Rats - ฉันคิดว่าฉันอาจจะพบวิธีแก้ปัญหาที่ฉลาดแทนฉันเปิดโปงข้อบกพร่องของคอมไพเลอร์คู่ ...


#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
ไม่มันไม่ได้มาตรฐาน แต่ฉันคิดว่ามันจะทำงานใน GCC ถ้าคุณเปิดตัวเลือก -fpermissive
Leon Timmermans

ฉันรู้ว่าความคิดเห็นไม่ได้มีที่ว่างมากมาย แต่คุณสามารถชี้ไปที่ข้อมูลว่าทำไมมันไม่เป็นไปตามมาตรฐาน? (ฉันไม่เถียง - ฉันอยากรู้อยากเห็น)
Michael Burr

Mike B: มาตรฐานบอกไว้ใน 3.10 p15: "หากโปรแกรมพยายามเข้าถึงค่าที่เก็บไว้ของวัตถุผ่าน lvalue นอกเหนือจากประเภทใดประเภทหนึ่งต่อไปนี้พฤติกรรมจะไม่ได้กำหนด" และรายการดังกล่าวจะไม่รวมกรณีของคุณ ทำ.
Johannes Schaub - litb

4
ฉันไม่แน่ใจว่าทำไมมันไม่ได้เพิ่มความคิดเห็นอื่นของฉัน: การโทรไปยังการรับสายของคุณไม่มีเงื่อนไข ดังนั้นมันจะเรียกใช้ฟังก์ชันฟรีเสมอและจะไม่มีฟังก์ชันใดในฐานเนื่องจาก baseclass ขึ้นอยู่กับพารามิเตอร์ประเภทเทมเพลต
Johannes Schaub - litb

@litb: ขอบคุณสำหรับพอยน์เตอร์ ฉันไม่คิดว่าจะใช้ 3.10 ตรงนี้ การเรียก toString () ด้านในของ doToString () ไม่ได้ "เข้าถึงค่าที่เก็บไว้ของวัตถุผ่าน lvalue" แต่ความคิดเห็นที่ 2 ของคุณถูกต้อง ฉันจะอัปเดตคำตอบ
Michael Burr

6

โซลูชัน C ++ มาตรฐานที่นำเสนอโดย litb นี้จะไม่ทำงานอย่างที่คาดไว้หากวิธีนี้เกิดขึ้นเพื่อกำหนดไว้ในคลาสฐาน

สำหรับวิธีแก้ปัญหาที่จัดการกับสถานการณ์นี้อ้างถึง:

ในรัสเซีย: http://www.rsdn.ru/forum/message/2759773.1.aspx

แปลภาษาอังกฤษโดย Roman.Perepelitsa: http://groups.google.com/group/comp.lang.c++.moderated/tree/browse_frm/thread/4f7c7a96f9afbe44/c95a7b4c645e449f?pli=1

มันฉลาดอย่างชาญฉลาด อย่างไรก็ตามปัญหาหนึ่งของ solutiion นี้คือให้ข้อผิดพลาดคอมไพเลอร์หากประเภทที่กำลังทดสอบเป็นสิ่งที่ไม่สามารถใช้เป็นคลาสพื้นฐาน (เช่นประเภทดั้งเดิม)

ใน Visual Studio ฉันสังเกตเห็นว่าถ้าทำงานกับวิธีที่ไม่มีข้อโต้แย้งต้องมีการเพิ่มคู่ที่ซ้ำซ้อน () ซ้ำรอบอาร์กิวเมนต์เพื่ออนุมาน () ในนิพจน์ขนาด


อืมฉันได้พัฒนาเวอร์ชั่นของฉันเองโดยใช้ไอเดียที่โพสต์ฉันพบว่าความคิดนั้นมีข้อบกพร่องอื่น ๆ ดังนั้นฉันจึงลบรหัสออกจากคำตอบของฉันอีกครั้ง หนึ่งคือฟังก์ชั่นทั้งหมดจะต้องเป็นสาธารณะในประเภทเป้าหมาย ดังนั้นคุณไม่สามารถตรวจสอบฟังก์ชั่น "f" ในสิ่งนี้: struct g { void f(); private: void f(int); };เพราะหนึ่งในฟังก์ชั่นส่วนตัว (นี่เป็นเพราะรหัสทำusing g::f;ซึ่งทำให้มันล้มเหลวหากfไม่สามารถเข้าถึงได้)
Johannes Schaub - litb

6

MSVC มีคำหลัก __if_exists และ __if_not_exists ( หมอ ) เมื่อใช้ร่วมกับวิธี typeof-SFINAE ของ Nicola ฉันสามารถสร้างการตรวจสอบสำหรับ GCC และ MSVC แบบที่ OP ต้องการ

การปรับปรุง:แหล่งที่มาสามารถพบได้ที่นี่


6

ตัวอย่างการใช้ SFINAE และเท็มเพลตความเชี่ยวชาญบางส่วนโดยการเขียนHas_fooเช็คแนวคิด:

#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

ฉันแก้ไขโซลูชันที่ให้ไว้ในhttps://stackoverflow.com/a/264088/2712152เพื่อทำให้เป็นเรื่องทั่วไปมากขึ้น และเนื่องจากมันไม่ได้ใช้คุณสมบัติ C ++ 11 ใหม่ใด ๆ เราจึงสามารถใช้กับคอมไพเลอร์รุ่นเก่าได้และควรทำงานกับ msvc ด้วย แต่คอมไพเลอร์ควรเปิดใช้งาน C99 เพื่อใช้สิ่งนี้เนื่องจากใช้มาโครแบบแปรผัน

แมโครต่อไปนี้สามารถใช้เพื่อตรวจสอบว่าคลาสใดมี typedef เฉพาะหรือไม่

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

แมโครต่อไปนี้สามารถใช้เพื่อตรวจสอบว่าคลาสใดมีฟังก์ชันสมาชิกเฉพาะหรือไม่กับอาร์กิวเมนต์จำนวนที่กำหนด

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

เราสามารถใช้มาโคร 2 ตัวด้านบนเพื่อทำการตรวจสอบ has_typedef และ has_mem_func เป็น:

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

คุณสามารถปรับปรุงสิ่งนี้เพื่อสนับสนุนฟังก์ชั่นสมาชิกด้วยอาร์กิวเมนต์แม่แบบ เปลี่ยนเทมเพลต <typename T> เป็นเทมเพลต <typename T, typename ... Args> จากนั้นคุณสามารถใช้ "Args ... " ในลิปของแมโครเพื่อสร้าง check check ที่มีเทมเพลต variadic เช่น. ตรวจหาวิธี "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" ); };
59

4

ไม่มีใครแปลกที่แนะนำเคล็ดลับที่ดีต่อไปนี้ที่ฉันเคยเห็นบนเว็บไซต์นี้:

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

คุณต้องทำให้แน่ใจว่า T เป็นคลาส ดูเหมือนว่าความคลุมเครือในการค้นหาของ foo คือความล้มเหลวในการทดแทน ฉันทำให้มันทำงานบน gcc ไม่แน่ใจว่ามันเป็นมาตรฐาน


3

เทมเพลตทั่วไปที่สามารถใช้ตรวจสอบได้ว่าประเภท "ฟีเจอร์" นั้นรองรับหรือไม่:

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

แม่แบบที่ตรวจสอบว่ามีวิธีการfooที่เข้ากันได้กับลายเซ็นdouble(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*>())));

ตัวอย่าง

// 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-crooked.com/a/83c6a631ed42cea4


จะมีวิธีการแบบอินไลน์ลงในแม่แบบของการโทรhas_foo is_supportedสิ่งที่ฉันต้องการคือการเรียกสิ่งที่ชอบ: std::cout << is_supported<magic.foo(), struct1>::value << std::endl;. สาเหตุของสิ่งนี้ฉันต้องการกำหนดhas_fooลายเซ็นของแต่ละฟังก์ชันที่แตกต่างกันที่ฉันต้องการตรวจสอบก่อนที่ฉันจะตรวจสอบฟังก์ชันได้หรือไม่
CJCombrink

2

วิธีการแก้ปัญหานี้?

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

ล้มเหลวหากtoStringมีการใช้งานมากเกินไปตามที่&U::toStringคลุมเครือ
Yakk - Adam Nevraumont

@Yakk ฉันคิดว่านักแสดงสามารถแก้ไขปัญหานี้ได้
user1095108

2

มีคำตอบมากมายที่นี่ แต่ฉันล้มเหลวในการค้นหาเวอร์ชันที่ทำการสั่งซื้อการแก้ปัญหาด้วยวิธีจริงในขณะที่ไม่ได้ใช้คุณสมบัติ c ++ ที่ใหม่กว่า (เฉพาะที่ใช้คุณสมบัติ c ++ 98 เท่านั้น)
หมายเหตุ: รุ่นนี้ผ่านการทดสอบและทำงานกับ vc ++ 2013, g ++ 5.2.0 และคอมไพเลอร์ onlline

ดังนั้นฉันจึงมาพร้อมกับรุ่นที่ใช้ 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,"");

การสาธิตสด (พร้อมการตรวจสอบประเภทผลตอบแทนเพิ่มเติมและวิธีแก้ไขปัญหา vc ++ 2010): http://cpp.sh/5b2vs

ไม่มีแหล่งข้อมูลในขณะที่ฉันมาด้วยตัวเอง

เมื่อเรียกใช้การสาธิตสดบนคอมไพเลอร์ g ++ โปรดทราบว่าอนุญาตให้มีขนาดอาเรย์เป็น 0 ซึ่งหมายความว่า static_assert ที่ใช้จะไม่ก่อให้เกิดข้อผิดพลาดของคอมไพเลอร์แม้ว่าจะล้มเหลวก็ตาม
วิธีแก้ไขที่ใช้กันทั่วไปคือการแทนที่ 'typedef' ในแมโครด้วย 'extern'


ไม่ แต่ฉันประกาศด้วยตัวเองและไม่ได้ใช้ rvalue (ดูที่ด้านบนของรหัสของฉัน) หรือคุณสามารถโน้มน้าวตัวเองและทดลองใช้การสาธิตสดในโหมด c ++ 98 PS: static_assert ไม่ได้เป็น c ++ 98 อย่างใดอย่างหนึ่ง แต่มีวิธีแก้ปัญหา (การสาธิตสด)
3296587

d'โอ้! คิดถึงว่า :-)
Ian Ni-Lewis

การยืนยันแบบคงที่ของคุณไม่ทำงาน คุณต้องใช้ขนาดอาร์เรย์ -1 แทน 0 (ลองวางstatic_assert(false);) ฉันใช้สิ่งนี้ในการเชื่อมต่อกับ CRTP ที่ฉันต้องการตรวจสอบว่าคลาสที่ได้รับมีฟังก์ชั่นเฉพาะ - ซึ่งกลายเป็นว่าไม่ได้ทำงาน แต่การยืนยันของคุณผ่านไปแล้วเสมอ ฉันทำผมหายไป
สุกร

ฉันสมมติว่าคุณกำลังใช้ g ++ โปรดทราบว่า gcc / g ++ มีส่วนขยายที่อนุญาตให้มีอาร์เรย์ขนาดศูนย์ ( gcc.gnu.org/onlinedocs/gcc/Zero-Length.html )
user3296587

คุณสามารถเขียนสิ่งนี้ใหม่เพื่อไม่ให้ตัวดำเนินการโอเวอร์โหลดได้หรือไม่? เช่นเลือกผู้ให้บริการรายอื่น? นอกจากนี้หลีกเลี่ยงมลภาวะของเนมสเปซด้วยสิ่งอื่นใดนอกจาก has_awesome_member หรือไม่
einpoklum

1

นี่คือเวอร์ชั่นของฉันที่จัดการกับฟังก์ชั่นสมาชิกที่เป็นไปได้ทั้งหมดด้วย arity โดยพลการรวมถึงฟังก์ชั่นสมาชิกเทมเพลตซึ่งอาจมีอาร์กิวเมนต์เริ่มต้น มันแตกต่าง 3 สถานการณ์พิเศษร่วมกันเมื่อทำการเรียกฟังก์ชั่นสมาชิกบางประเภทคลาสกับประเภท arg ที่กำหนด: (1) ที่ถูกต้องหรือ (2) คลุมเครือหรือ (3) ไม่สามารถทำงานได้ ตัวอย่างการใช้งาน:

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

ตอนนี้คุณสามารถใช้มันได้เช่นนี้:

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

นี่คือโค้ดที่เขียนใน c ++ 11 อย่างไรก็ตามคุณสามารถพอร์ตได้อย่างง่ายดาย (โดยมีการปรับแต่งเล็กน้อย) ไปยัง non-c ++ 11 ที่มีส่วนขยาย typeof (เช่น gcc) คุณสามารถแทนที่แมโคร HAS_MEM ด้วยตนเองได้

#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

คุณสามารถข้ามเมตาแกรมโปรแกรมทั้งหมดใน C ++ 14 และเพียงแค่เขียนสิ่งนี้โดยใช้fit::conditionalจากFit library:

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

คุณยังสามารถสร้างฟังก์ชั่นได้โดยตรงจาก 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"; }
);

อย่างไรก็ตามหากคุณใช้คอมไพเลอร์ที่ไม่รองรับ lambdas ทั่วไปคุณจะต้องเขียนฟังก์ชันของวัตถุแยกต่างหาก:

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
มันง่ายแค่ไหนที่จะเขียนสิ่งนี้เพื่อไม่ให้ขึ้นกับfitหรือไลบรารีใด ๆ ที่ไม่ใช่มาตรฐาน?
einpoklum

1

ด้วย C ++ 20 คุณสามารถเขียนสิ่งต่อไปนี้:

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

นี่คือตัวอย่างของรหัสการทำงาน

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>* = nullptrจะช่วยให้การทำงานซึ่งจะเสริมintการโต้แย้งซึ่งมีความสำคัญมากกว่าฟังก์ชั่นซึ่งจะเรียกว่าเมื่อมีlong0

คุณสามารถใช้หลักการเดียวกันสำหรับฟังก์ชั่นที่ส่งคืนtrueหากมีการใช้งานฟังก์ชั่น

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

ฉันมีปัญหาที่คล้ายกัน:

คลาสเทมเพลตที่อาจได้รับมาจากคลาสพื้นฐานบางคลาสที่มีสมาชิกที่แน่นอนและอื่น ๆ ที่ไม่มี

ฉันแก้ไขมันคล้ายกับคำตอบ "typeof" (ของ Nicola Bonelli) แต่ด้วย dentype เพื่อให้คอมไพล์และทำงานอย่างถูกต้องใน 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

อีกวิธีหนึ่งในการทำใน C ++ 17 (แรงบันดาลใจจากการเพิ่ม: hana)

เขียนครั้งเดียวและใช้หลายครั้ง มันไม่จำเป็นต้องhas_something<T>เรียนประเภทของลักษณะ

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

ตัวอย่าง

#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
"เราไม่ต้องการคำอธิบายคำตอบ" ... โปรดเพิ่มคำอธิบายข้อมูลลงในคำตอบของคุณเพื่อปรับปรุง ขอบคุณ
YesThatIsMyName
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.