วิธีการทำงานของ SFINAE ใน C ++


40

ฉันใช้ฟังก์ชั่น SFINAE อย่างหนักในโครงการและไม่แน่ใจว่ามีความแตกต่างใด ๆ ระหว่างสองวิธีต่อไปนี้ (นอกเหนือจากสไตล์):

#include <cstdlib>
#include <type_traits>
#include <iostream>

template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo()
{
    std::cout << "method 1" << std::endl;
}

template <class T, std::enable_if_t<std::is_same_v<T, double>>* = 0>
void foo()
{
    std::cout << "method 2" << std::endl;
}

int main()
{
    foo<int>();
    foo<double>();

    std::cout << "Done...";
    std::getchar();

    return EXIT_SUCCESS;
}

ผลลัพธ์ของโปรแกรมเป็นไปตามที่คาดไว้:

method 1
method 2
Done...

ฉันเคยเห็นวิธีที่ 2 ใช้บ่อยใน stackoverflow แต่ฉันชอบวิธีที่ 1

มีสถานการณ์ใดบ้างที่ทั้งสองแนวทางนี้ต่างกัน?


คุณรันโปรแกรมนี้อย่างไร? มันจะไม่รวบรวมสำหรับฉัน
เปลี่ยน igel

@alter igel มันจะต้องมีคอมไพเลอร์ C ++ 17 ฉันใช้ MSVC 2019 เพื่อทดสอบตัวอย่างนี้ แต่ฉันทำงานกับ Clang เป็นหลัก
คี ธ

ที่เกี่ยวข้อง: ทำไมควร-I-หลีกเลี่ยง stdenable ถ้าในฟังก์ชั่นลายเซ็นและ C ++ 20 เปิดตัวยังมีวิธีการใหม่ที่มีแนวคิด :-)
Jarod42

@ Jarod42 Concept เป็นสิ่งที่ฉันต้องการมากที่สุดสำหรับ C ++ 20
พูดว่า Reinstate Monica

คำตอบ:


35

ฉันเคยเห็นวิธีที่ 2 ใช้บ่อยใน stackoverflow แต่ฉันชอบวิธีที่ 1

คำแนะนำ: ชอบวิธีที่ 2

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

สมมติว่าคุณต้องการเปิดใช้งานfoo()รุ่นที่ 1 เมื่อbar<T>()(แสร้งทำเป็นว่ามันเป็นconstexprฟังก์ชั่น) เป็นtrueและfoo()รุ่นที่ 2 เมื่อเป็นbar<T>()false

กับ

template <typename T, typename = std::enable_if_t<true == bar<T>()>>
void foo () // version 1
 { }

template <typename T, typename = std::enable_if_t<false == bar<T>()>>
void foo () // version 2
 { }

คุณได้รับข้อผิดพลาดในการคอมไพล์เนื่องจากคุณมีความกำกวม: สองfoo()ฟังก์ชันที่มีลายเซ็นเดียวกัน (พารามิเตอร์เทมเพลตเริ่มต้นไม่เปลี่ยนลายเซ็น)

แต่ทางออกต่อไปนี้

template <typename T, std::enable_if_t<true == bar<T>(), bool> = true>
void foo () // version 1
 { }

template <typename T, std::enable_if_t<false == bar<T>(), bool> = true>
void foo () // version 2
 { }

ใช้งานได้เพราะ SFINAE ปรับเปลี่ยนลายเซ็นของฟังก์ชัน

การสังเกตที่ไม่เกี่ยวข้อง: นอกจากนี้ยังมีวิธีที่สาม: เปิดใช้งาน / ปิดใช้งานชนิดส่งคืน (ยกเว้นตัวสร้างคลาส / โครงสร้าง

template <typename T>
std::enable_if_t<true == bar<T>()> foo () // version 1
 { }

template <typename T>
std::enable_if_t<false == bar<T>()> foo () // version 2
 { }

เป็นวิธีที่ 2 วิธีที่ 3 เข้ากันได้กับการเลือกฟังก์ชั่นทางเลือกที่มีลายเซ็นเดียวกัน


1
ขอบคุณสำหรับคำอธิบายที่ดีฉันจะชอบวิธีที่ 2 และ 3 จากนี้ไป :-)
keith

"พารามิเตอร์เทมเพลตเริ่มต้นไม่เปลี่ยนลายเซ็น" - ความแตกต่างนี้ในชุดที่สองของคุณแตกต่างกันอย่างไรซึ่งใช้พารามิเตอร์แม่แบบเริ่มต้นด้วย
Eric

1
@ Eric - ไม่ใช่เรื่องง่ายที่จะบอกว่า ... ผมคิดว่าคำตอบอื่น ๆ อธิบายที่ดีกว่านี้ ... ถ้า SFINAE เปิด / ปิดอาร์กิวเมนต์แม่แบบเริ่มต้นที่foo()ฟังก์ชั่นยังคงมีอยู่เมื่อคุณเรียกมันด้วยอย่างชัดเจนสองแม่แบบพารามิเตอร์ (คนfoo<double, double>();โทร) และหากยังคงมีอยู่จะมีความกำกวมกับรุ่นอื่น ด้วยวิธีที่ 2 SFINAE เปิดใช้งาน / ปิดใช้งานอาร์กิวเมนต์ที่สองไม่ใช่พารามิเตอร์เริ่มต้น ดังนั้นคุณไม่สามารถเรียกได้ว่าเป็นการอธิบายพารามิเตอร์เนื่องจากมีความล้มเหลวในการทดแทนที่ไม่อนุญาตให้มีพารามิเตอร์ที่สอง ดังนั้นรุ่นจึงไม่สามารถใช้งานได้ดังนั้นจึงไม่มีความกำกวม
max66

3
วิธีที่ 3 มีข้อได้เปรียบเพิ่มเติมโดยทั่วไปที่ไม่รั่วเข้าไปในชื่อสัญลักษณ์ ตัวแปรauto foo() -> std::enable_if_t<...>มักมีประโยชน์ในการหลีกเลี่ยงการซ่อนฟังก์ชัน - ลายเซ็นและอนุญาตให้ใช้ฟังก์ชัน - อาร์กิวเมนต์
Deduplicator

@ max66: ดังนั้นประเด็นสำคัญคือความล้มเหลวในการแทนที่ค่าเริ่มต้นในพารามิเตอร์เทมเพลตไม่ใช่ข้อผิดพลาดหากมีการระบุพารามิเตอร์และไม่จำเป็นต้องมีค่าเริ่มต้น
เอริค

21

นอกจากคำตอบของ max66แล้วเหตุผลอีกข้อหนึ่งที่ชอบวิธีที่ 2 ก็คือด้วยวิธีที่ 1 คุณสามารถ (โดยไม่ตั้งใจ) ผ่านพารามิเตอร์ประเภทที่ชัดเจนว่าเป็นอาร์กิวเมนต์เท็มเพลตที่สองและเอาชนะกลไก SFINAE ได้อย่างสมบูรณ์ สิ่งนี้อาจเกิดขึ้นได้จากข้อผิดพลาดที่พิมพ์ผิดคัดลอก / วางหรือการกำกับดูแลในกลไกแม่แบบที่มีขนาดใหญ่กว่า

#include <cstdlib>
#include <type_traits>
#include <iostream>

// NOTE: foo should only accept T=int
template <class T, class = std::enable_if_t<std::is_same_v<T, int>>>
void foo(){
    std::cout << "method 1" << std::endl;
}

int main(){

    // works fine
    foo<int>();

    // ERROR: subsitution failure, as expected
    // foo<double>();

    // Oops! also works, even though T != int :(
    foo<double, double>();

    return 0;
}

การสาธิตสดที่นี่


จุดดี. กลไกสามารถถูกแย่งชิง
max66
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.