ความแตกต่างระหว่าง std :: result_of และ Decltype


100

ฉันมีปัญหาในการทำความเข้าใจความจำเป็นstd::result_ofใน C ++ 0x ถ้าฉันเข้าใจถูกต้องresult_ofใช้เพื่อรับชนิดผลลัพธ์ของการเรียกใช้อ็อบเจ็กต์ฟังก์ชันด้วยพารามิเตอร์บางประเภท ตัวอย่างเช่น:

template <typename F, typename Arg>
typename std::result_of<F(Arg)>::type
invoke(F f, Arg a)
{
    return f(a);
}

ฉันไม่เห็นความแตกต่างกับรหัสต่อไปนี้:

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(f(a)) //uses the f parameter
{
    return f(a);
}

หรือ

template <typename F, typename Arg>
auto invoke(F f, Arg a) -> decltype(F()(a)); //"constructs" an F
{
    return f(a);
}

ปัญหาเดียวที่ฉันเห็นจากสองวิธีนี้คือเราต้องทำอย่างใดอย่างหนึ่ง:

  • มีอินสแตนซ์ของ functor เพื่อใช้ในนิพจน์ที่ส่งผ่านไปยังเดสก์ไทป์
  • รู้จักตัวสร้างที่กำหนดไว้สำหรับ functor

ฉันคิดถูกไหมว่าความแตกต่างเพียงอย่างเดียวระหว่างdecltypeและresult_ofข้อแรกต้องการนิพจน์ในขณะที่ข้อที่สองไม่ต้องการ?

คำตอบ:


86

result_ofได้รับการแนะนำใน Boostและรวมอยู่ใน TR1และสุดท้ายใน C ++ 0x จึงresult_ofมีข้อได้เปรียบที่เข้ากันได้แบบย้อนหลัง (มีไลบรารีที่เหมาะสม).

decltype เป็นสิ่งใหม่ทั้งหมดใน C ++ 0x ไม่ได้ จำกัด เฉพาะการส่งคืนประเภทของฟังก์ชันและเป็นคุณลักษณะของภาษา


อย่างไรก็ตามบน gcc 4.5 result_ofถูกนำไปใช้ในแง่ของdecltype:

  template<typename _Signature>
    class result_of;

  template<typename _Functor, typename... _ArgTypes>
    struct result_of<_Functor(_ArgTypes...)>
    {
      typedef
        decltype( std::declval<_Functor>()(std::declval<_ArgTypes>()...) )
        type;
    };

4
เท่าที่ฉันเข้าใจdecltypeนั้นน่าเกลียดกว่า แต่ก็มีพลังมากกว่าด้วย result_ofสามารถใช้ได้เฉพาะกับประเภทที่เรียกได้และต้องใช้ประเภทเป็นอาร์กิวเมนต์ ตัวอย่างเช่นคุณไม่สามารถใช้result_ofที่นี่: template <typename T, typename U> auto sum( T t, U u ) -> decltype( t + u );ถ้าอาร์กิวเมนต์สามารถเป็นประเภทเลขคณิตได้ (ไม่มีฟังก์ชันFที่คุณสามารถกำหนดF(T,U)ให้เป็นตัวแทนt+uได้สำหรับประเภทที่ผู้ใช้กำหนดคุณสามารถทำได้ในทำนองเดียวกัน (ฉันไม่ได้เล่นด้วยจริงๆ) ฉันคิดว่า การโทรไปยังเมธอดสมาชิกอาจทำได้ยากresult_ofโดยไม่ต้องใช้สารยึดเกาะหรือ
แลมบ์ดาส

2
หมายเหตุหนึ่งรายการประเภทปฏิเสธต้องการอาร์กิวเมนต์เพื่อเรียกใช้ฟังก์ชันด้วย AFAIK ดังนั้นหากไม่มี result_of <> จึงไม่สะดวกที่จะรับประเภทที่ส่งคืนโดยเทมเพลตโดยไม่ต้องอาศัยอาร์กิวเมนต์ที่มีตัวสร้างเริ่มต้นที่ถูกต้อง
Robert Mason

3
@RobertMason: อาร์กิวเมนต์เหล่านี้สามารถเรียกคืนได้โดยใช้std::declvalเช่นรหัสที่ฉันแสดงไว้ด้านบน แน่นอนว่ามันน่าเกลียด :)
kennytm

1
@ DavidRodríguez-dribeas ความคิดเห็นของคุณมีวงเล็บที่ยังไม่ปิดซึ่งเปิดด้วย "(มี" :(
Navin

1
หมายเหตุอีกประการหนึ่ง: result_ofและประเภทผู้ช่วยเหลือresult_of_tจะเลิกใช้งานเมื่อ C ++ 17 เพื่อสนับสนุนinvoke_resultและinvoke_result_tบรรเทาข้อ จำกัด บางประการของอดีต เหล่านี้มีการระบุไว้ที่ด้านล่างของen.cppreference.com/w/cpp/types/result_of
sigma

11

หากคุณต้องการประเภทของสิ่งที่ไม่ใช่การเรียกใช้ฟังก์ชันstd::result_ofก็ไม่ต้องใช้ decltype()สามารถระบุประเภทของนิพจน์ใดก็ได้

หากเรา จำกัด ตัวเองเพียงวิธีต่างๆในการกำหนดประเภทการส่งคืนของการเรียกใช้ฟังก์ชัน (ระหว่างstd::result_of_t<F(Args...)>และdecltype(std::declval<F>()(std::declval<Args>()...)) ก็จะมีความแตกต่าง

std::result_of<F(Args...) ถูกกำหนดให้เป็น:

ถ้านิพจน์ INVOKE (declval<Fn>(), declval<ArgTypes>()...)ถูกสร้างขึ้นอย่างดีเมื่อถือว่าเป็นตัวถูกดำเนินการที่ไม่ได้ประเมิน (ข้อ 5) ประเภทสมาชิกจะตั้งชื่อประเภทเป็นdecltype(INVOKE (declval<Fn>(), declval<ArgTypes>()...)); อย่างอื่นจะไม่มีประเภทสมาชิก

ความแตกต่างระหว่างresult_of<F(Args..)>::typeและdecltype(std::declval<F>()(std::declval<Args>()...)เป็นข้อมูลเกี่ยวกับสิ่งINVOKEนั้น การใช้declval/ decltypeโดยตรงนอกจากจะพิมพ์ได้นานขึ้นเล็กน้อยแล้วจะใช้ได้Fก็ต่อเมื่อสามารถเรียกใช้ได้โดยตรงเท่านั้น (ประเภทอ็อบเจ็กต์ฟังก์ชันหรือฟังก์ชันหรือตัวชี้ฟังก์ชัน) result_ofนอกจากนี้ยังสนับสนุนตัวชี้ไปยังฟังก์ชันสมาชิกและตัวชี้ไปยังข้อมูลสมาชิก

ในขั้นต้นการใช้declval/ decltypeรับประกันนิพจน์ที่เป็นมิตรกับ SFINAE ในขณะที่std::result_ofอาจทำให้คุณเกิดข้อผิดพลาดอย่างหนักแทนการหักล้มเหลว ที่ได้รับการแก้ไขใน C ++ 14: std::result_ofตอนนี้จำเป็นต้องเป็นมิตรกับ SFINAE (ขอบคุณเอกสารนี้ )

ดังนั้นในคอมไพเลอร์ C ++ 14 ที่สอดคล้องกันstd::result_of_t<F(Args...)>นั้นเหนือกว่าอย่างเคร่งครัด มันชัดเจนสั้นและถูกต้องสนับสนุนเพิ่มเติมFs ‡


เว้นแต่ว่าคุณกำลังใช้มันในบริบทที่คุณไม่ต้องการให้ตัวชี้แก่สมาชิกดังนั้นstd::result_of_tจะประสบความสำเร็จในกรณีที่คุณอาจต้องการให้มันล้มเหลว

มีข้อยกเว้น ในขณะที่มันสนับสนุนตัวชี้ไปยังสมาชิกresult_ofจะไม่ทำงานหากคุณพยายามที่จะยกตัวอย่างที่ไม่ถูกต้องประเภท-ID สิ่งเหล่านี้จะรวมถึงฟังก์ชันที่ส่งคืนฟังก์ชันหรือประเภทนามธรรมตามค่า เช่น:

template <class F, class R = result_of_t<F()>>
R call(F& f) { return f(); }

int answer() { return 42; }

call(answer); // nope

การใช้งานที่ถูกต้องจะได้รับแต่นั่นคือรายละเอียดที่คุณไม่ต้องจำไว้ด้วยresult_of_t<F&()>decltype


สำหรับประเภทที่ไม่อ้างอิงTและฟังก์ชันtemplate<class F> result_of_t<F&&(T&&)> call(F&& f, T&& arg) { return std::forward<F>(f)(std::move(arg)); }การใช้งานresult_of_tถูกต้องหรือไม่?
Zizheng Tai

นอกจากนี้ถ้าอาร์กิวเมนต์ที่เราส่งผ่านไปfเป็น a const Tเราควรใช้result_of_t<F&&(const T&)>?
Zizheng Tai
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.