std :: enable_if เพื่อคอมไพล์ฟังก์ชันสมาชิกแบบมีเงื่อนไข


156

std::enable_ifฉันพยายามที่จะได้รับตัวอย่างง่ายๆในการทำงานจะเข้าใจวิธีการใช้งาน หลังจากที่ฉันอ่านคำตอบนี้ฉันคิดว่ามันไม่ควรยากเกินไปที่จะเกิดขึ้นกับตัวอย่างง่ายๆ ฉันต้องการใช้std::enable_ifเพื่อเลือกระหว่างสองฟังก์ชั่นสมาชิกและอนุญาตให้ใช้หนึ่งในนั้นเท่านั้น

น่าเสียดายที่สิ่งต่อไปนี้ไม่ได้รวบรวมกับ gcc 4.7 และหลังจากเวลาผ่านไปหลายชั่วโมงของการลองฉันถามพวกคุณว่าสิ่งที่ฉันผิดคืออะไร

#include <utility>
#include <iostream>

template< class T >
class Y {

    public:
        template < typename = typename std::enable_if< true >::type >
        T foo() {
            return 10;
        }
        template < typename = typename std::enable_if< false >::type >
        T foo() {
            return 10;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

gcc รายงานปัญหาต่อไปนี้:

% LANG=C make CXXFLAGS="-std=c++0x" enable_if
g++ -std=c++0x    enable_if.cpp   -o enable_if
enable_if.cpp:12:65: error: `type' in `struct std::enable_if<false>' does not name a type
enable_if.cpp:13:15: error: `template<class T> template<class> T Y::foo()' cannot be overloaded
enable_if.cpp:9:15: error: with `template<class T> template<class> T Y::foo()'

ทำไม g ++ ไม่ลบอินสแตนซ์ที่ไม่ถูกต้องสำหรับฟังก์ชันสมาชิกที่สอง? ตามมาตรฐานstd::enable_if< bool, T = void >::typeมีอยู่เฉพาะเมื่อพารามิเตอร์เทมเพลตบูลีนเป็นจริง แต่ทำไม g ++ ไม่ถือว่านี่เป็น SFINAE ฉันคิดว่าข้อความแสดงข้อผิดพลาดมากเกินไปมาจากปัญหาที่ g ++ ไม่ได้ลบฟังก์ชั่นสมาชิกที่สองและเชื่อว่าสิ่งนี้จะเป็นการโอเวอร์โหลด


1
ฉันไม่แน่ใจ แต่ฉันคิดว่ามันเป็นดังต่อไปนี้: enable_if ขึ้นอยู่กับ SFINAE (ความล้มเหลวในการทดแทนไม่ใช่ข้อผิดพลาด) อย่างไรก็ตามคุณไม่มีการทดแทนใด ๆ ที่นี่เนื่องจากไม่มีพารามิเตอร์ที่ไม่สามารถใช้เพื่อกำหนดว่าเกินพิกัดใดที่จะใช้ คุณควรจะทำให้ "ความจริง" คาดไม่ถึง "เท็จ" ขึ้นอยู่กับตัน (ฉันรู้ว่าคุณไม่ต้องการที่จะทำมันในตัวอย่างที่เรียบง่าย แต่มันอาจจะง่ายเกินไปในขณะนี้ ... )
ฟิลิปป์

3
ฉันคิดถึงสิ่งนั้นเช่นกันและพยายามใช้std::is_same< T, int >::valueและ! std::is_same< T, int >::valueสิ่งที่ให้ผลลัพธ์เดียวกัน
evnu

คำตอบ:


117

SFINAE ใช้งานได้เฉพาะในกรณีที่การทดแทนในการลดการโต้แย้งของแม่แบบอาร์กิวเมนต์ทำให้การสร้างที่ไม่ดี ไม่มีการทดแทนดังกล่าว

ฉันคิดถึงสิ่งนั้นเช่นกันและพยายามใช้std::is_same< T, int >::valueและ! std::is_same< T, int >::valueสิ่งที่ให้ผลลัพธ์เดียวกัน

นั่นเป็นเพราะเมื่อเทมเพลตของคลาสนั้นถูกทำให้เป็นอินสแตนซ์ (ซึ่งเกิดขึ้นเมื่อคุณสร้างอ็อบเจกต์ประเภทY<int>ในกรณีอื่น ๆ ) มันจะทำให้อินสแตนซ์ของการประกาศสมาชิกทั้งหมดของอินสแตนซ์นั้น ในหมู่พวกเขายังเป็นแม่แบบของสมาชิก ทราบว่าTเป็นที่รู้จักกันแล้วและ!std::is_same< T, int >::valueให้ผลเป็นเท็จ ดังนั้นมันจะสร้างคลาสY<int>ที่มี

class Y<int> {
    public:
        /* instantiated from
        template < typename = typename std::enable_if< 
          std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< true >::type >
        int foo();

        /* instantiated from

        template < typename = typename std::enable_if< 
          ! std::is_same< T, int >::value >::type >
        T foo() {
            return 10;
        }
        */

        template < typename = typename std::enable_if< false >::type >
        int foo();
};

การstd::enable_if<false>::typeเข้าถึงชนิดที่ไม่มีอยู่เพื่อให้การประกาศไม่ถูกต้อง และโปรแกรมของคุณไม่ถูกต้อง

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


14
... เพื่อชี้แจงในกรณีที่มีประโยชน์: เมื่ออินสแตนซ์ของYคลาสเทมเพลตถูกสร้างอินสแตนซ์คอมไพเลอร์จะไม่รวบรวมฟังก์ชันสมาชิกเทมเพลตจริง ๆ อย่างไรก็ตามคอมไพเลอร์จะดำเนินการแทนTลงในแม่แบบสมาชิก DECLARATIONS เพื่อให้แม่แบบสมาชิกเหล่านี้สามารถอินสแตนซ์ได้ในภายหลัง จุดที่ล้มเหลวนี้ไม่ใช่ SFINAE เนื่องจาก SFINAE จะใช้เฉพาะเมื่อพิจารณาชุดของฟังก์ชันที่เป็นไปได้สำหรับการแก้ปัญหาการโอเวอร์โหลดและการสร้างอินสแตนซ์ของคลาสไม่ใช่กรณีของการพิจารณาชุดของฟังก์ชันสำหรับการแก้ปัญหาการโอเวอร์โหลด (หรืออย่างนั้นฉันคิด!)
Dan Nissenbaum

93

ฉันทำตัวอย่างสั้น ๆ นี้ซึ่งยังใช้งานได้

#include <iostream>
#include <type_traits>

class foo;
class bar;

template<class T>
struct is_bar
{
    template<class Q = T>
    typename std::enable_if<std::is_same<Q, bar>::value, bool>::type check()
    {
        return true;
    }

    template<class Q = T>
    typename std::enable_if<!std::is_same<Q, bar>::value, bool>::type check()
    {
        return false;
    }
};

int main()
{
    is_bar<foo> foo_is_bar;
    is_bar<bar> bar_is_bar;
    if (!foo_is_bar.check() && bar_is_bar.check())
        std::cout << "It works!" << std::endl;

    return 0;
}

แสดงความคิดเห็นหากคุณต้องการให้ฉันทำอย่างละเอียด ฉันคิดว่ารหัสนี้เป็นคำอธิบายที่อธิบายตัวเองไม่มากก็น้อย แต่แล้วฉันก็ทำมันขึ้นมาอีกครั้งดังนั้นฉันอาจผิด :)

คุณสามารถดูได้ในการดำเนินการที่นี่


2
สิ่งนี้ไม่ได้รวบรวมใน VS2012 error C4519: default template arguments are only allowed on a class template.
PythonNut

1
นั่นเป็นโชคร้าย ฉันทดสอบมันด้วย gcc เท่านั้น อาจจะช่วยได้: stackoverflow.com/a/17543296/660982
jpihl

1
นี่เป็นคำตอบที่ดีที่สุดที่นี่และสิ่งที่ฉันกำลังมองหา
Weipeng L

3
เหตุใดจึงมีความจำเป็นต้องสร้างเทมเพลตคลาสQใหม่แม้ว่าจะเท่ากับTหรือไม่
ilya1725

1
เพราะคุณต้องการเทมเพลตtestฟังก์ชันสมาชิก ทั้งสองไม่สามารถอยู่ได้ในเวลาเดียวกัน เพียงแค่ส่งประเภทคลาสแม่แบบQ Tคุณสามารถลบเทมเพลตชั้นเรียนได้Tเช่น: cpp.sh/4nxwแต่จุดประสงค์นั้นเอาชนะวัตถุประสงค์ได้
jpihl

13

สำหรับผู้มาสายที่กำลังมองหาทางออกที่ "เพิ่งได้ผล":

#include <utility>
#include <iostream>

template< typename T >
class Y {

    template< bool cond, typename U >
    using resolvedType  = typename std::enable_if< cond, U >::type; 

    public:
        template< typename U = T > 
        resolvedType< true, U > foo() {
            return 11;
        }
        template< typename U = T >
        resolvedType< false, U > foo() {
            return 12;
        }

};


int main() {
    Y< double > y;

    std::cout << y.foo() << std::endl;
}

รวบรวมกับ:

g++ -std=gnu++14 test.cpp 

วิ่งให้:

./a.out 
11

6
อืมเหตุผลที่คุณจะเปลี่ยนชื่อไปstd::enable_if_t resolvedType
Qwertie

1
เพราะไม่ใช่ทุกคนสามารถใช้ C ++ 17 ได้ด้วยเหตุผลที่แตกต่างกันอย่างกว้างขวาง
James Yang

9

จากโพสต์นี้ :

อาร์กิวเมนต์แม่แบบเริ่มต้นไม่ได้เป็นส่วนหนึ่งของลายเซ็นของแม่แบบ

แต่เราสามารถทำสิ่งนี้:

#include <iostream>

struct Foo {
    template < class T,
               class std::enable_if < !std::is_integral<T>::value, int >::type = 0 >
    void f(const T& value)
    {
        std::cout << "Not int" << std::endl;
    }

    template<class T,
             class std::enable_if<std::is_integral<T>::value, int>::type = 0>
    void f(const T& value)
    {
        std::cout << "Int" << std::endl;
    }
};

int main()
{
    Foo foo;
    foo.f(1);
    foo.f(1.1);

    // Output:
    // Int
    // Not int
}

มันใช้งานได้ แต่นี่เป็นฟังก์ชั่นพื้นฐานที่ไม่ใช่เทมเพลตไม่ใช่คลาสเอง ... มันไม่อนุญาตให้วางหนึ่งในสองฟังก์ชันต้นแบบที่เหมือนกันทั้งสองอย่าง (เมื่อคุณต้องผ่านการโอเวอร์โหลด) อย่างไรก็ตามความคิดที่ดี คุณช่วยเขียนตัวอย่าง OP ในรูปแบบที่ใช้งานได้ไหม
user1284631

5

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

template< class T, bool condition> struct FooImpl;
template<class T> struct FooImpl<T, true> {
T foo() { return 10; }
};

template<class T> struct FoolImpl<T,false> {
T foo() { return 5; }
};

template< class T >
class Y : public FooImpl<T, boost::is_integer<T> > // whatever your test is goes here.
{
public:
    typedef FooImpl<T, boost::is_integer<T> > inherited;

    // you will need to use "inherited::" if you want to name any of the 
    // members of those inherited classes.
};

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

Ex:

template<class T, bool condition> class Goo;
// repeat pattern above.

template<class T, bool condition>
class Foo<T, true> : public Goo<T, boost::test<T> > {
public:
    typedef Goo<T, boost::test<T> > inherited:
    // etc. etc.
};

4

บูลีนต้องขึ้นอยู่กับพารามิเตอร์แม่แบบที่จะอนุมาน ดังนั้นวิธีง่าย ๆ ในการแก้ไขคือใช้พารามิเตอร์บูลีนที่เป็นค่าเริ่มต้น:

template< class T >
class Y {

    public:
        template < bool EnableBool = true, typename = typename std::enable_if<( std::is_same<T, double>::value && EnableBool )>::type >
        T foo() {
            return 10;
        }

};

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

template< class T >
class Y {

    public:
        TICK_MEMBER_REQUIRES(std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

        TICK_MEMBER_REQUIRES(!std::is_same<T, double>::value)
        T foo() {
            return 10;
        }

};

คุณยังสามารถใช้สมาชิกของคุณเองต้องการแมโครเช่นนี้ (ในกรณีที่คุณไม่ต้องการใช้ไลบรารีอื่น):

template<long N>
struct requires_enum
{
    enum class type
    {
        none,
        all       
    };
};


#define MEMBER_REQUIRES(...) \
typename requires_enum<__LINE__>::type PrivateRequiresEnum ## __LINE__ = requires_enum<__LINE__>::type::none, \
class=typename std::enable_if<((PrivateRequiresEnum ## __LINE__ == requires_enum<__LINE__>::type::none) && (__VA_ARGS__))>::type

มันไม่ได้ผลสำหรับฉันเช่นนั้น Maaybe มีบางอย่างหายไป? คุณช่วยเขียนตัวอย่าง OP ในรูปแบบที่ใช้งานได้ไหม
user1284631

ตัวอย่างดั้งเดิมไม่ทำงานกับการบรรทุกเกินพิกัด ฉันอัปเดตคำตอบแล้วคุณจะทำอย่างไรกับการโหลดมากเกินไป
Paul Fultz II

0

นี่คือตัวอย่างที่เรียบง่ายของฉันโดยใช้มาโคร ใช้วงเล็บคู่enable_if((...))เมื่อใช้นิพจน์ที่ซับซ้อนมากขึ้น

template<bool b, std::enable_if_t<b, int> = 0>
using helper_enable_if = int;

#define enable_if(value) typename = helper_enable_if<value>

struct Test
{
     template<enable_if(false)>
     void run();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.