std :: enable_if để biên dịch có điều kiện một hàm thành viên


156

Tôi đang cố gắng để có được một ví dụ đơn giản để làm việc để hiểu cách sử dụng std::enable_if. Sau khi tôi đọc câu trả lời này , tôi nghĩ rằng không quá khó để đưa ra một ví dụ đơn giản. Tôi muốn sử dụng std::enable_ifđể chọn giữa hai hàm thành viên và chỉ cho phép một trong số chúng được sử dụng.

Thật không may, những điều sau đây không được biên dịch với gcc 4.7 và sau nhiều giờ cố gắng, tôi đang hỏi các bạn rằng lỗi của tôi là gì.

#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 báo cáo các vấn đề sau:

% 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()'

Tại sao g ++ không xóa phần khởi tạo sai cho hàm thành viên thứ hai? Theo tiêu chuẩn, std::enable_if< bool, T = void >::typechỉ tồn tại khi tham số mẫu boolean là đúng. Nhưng tại sao g ++ không coi đây là SFINAE? Tôi nghĩ rằng thông báo lỗi quá tải xuất phát từ vấn đề g ++ không xóa chức năng thành viên thứ hai và tin rằng đây sẽ là một tình trạng quá tải.


1
Tôi không chắc chắn, nhưng tôi nghĩ đó là như sau: enable_if dựa trên SFINAE (lỗi thay thế không phải là lỗi). Tuy nhiên, bạn không có bất kỳ sự thay thế nào ở đây, bởi vì không có tham số nào không thể được sử dụng để xác định quá tải nào sẽ được sử dụng. Bạn nên làm cho "đúng" không "sai" phụ thuộc vào T. (Tôi biết bạn không muốn làm điều đó trong ví dụ đơn giản, nhưng có lẽ bây giờ quá đơn giản ...)
Philipp

3
Tôi cũng nghĩ về điều đó và cố gắng sử dụng std::is_same< T, int >::value! std::is_same< T, int >::valuecho kết quả tương tự.
evnu

Câu trả lời:


117

SFINAE chỉ hoạt động nếu sự thay thế trong suy luận đối số của đối số mẫu làm cho cấu trúc không được định dạng. Không có sự thay thế như vậy.

Tôi cũng nghĩ về điều đó và cố gắng sử dụng std::is_same< T, int >::value! std::is_same< T, int >::valuecho kết quả tương tự.

Đó là bởi vì khi mẫu lớp được khởi tạo (xảy ra khi bạn tạo một đối tượng kiểu Y<int>trong số các trường hợp khác), nó sẽ khởi tạo tất cả các khai báo thành viên của nó (không nhất thiết phải là định nghĩa / cơ thể của chúng!). Trong số đó cũng có các mẫu thành viên của nó. Lưu ý rằng Tđược biết sau đó, và !std::is_same< T, int >::valuemang lại sai. Vì vậy, nó sẽ tạo ra một lớp Y<int>có chứa

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

Các std::enable_if<false>::typetruy cập một loại không tồn tại, do đó khai báo là không đúng. Và do đó chương trình của bạn không hợp lệ.

Bạn cần tạo các mẫu thành viên ' enable_if phụ thuộc vào một tham số của chính mẫu thành viên. Sau đó các khai báo là hợp lệ, bởi vì toàn bộ loại vẫn còn phụ thuộc. Khi bạn cố gắng gọi một trong số họ, việc khấu trừ đối số cho các đối số mẫu của họ xảy ra và SFINAE xảy ra như mong đợi. Xem câu hỏi này và câu trả lời tương ứng về cách làm điều đó.


14
... Chỉ cần làm rõ, trong trường hợp nó hữu ích: Khi một thể hiện của Ylớp mẫu được khởi tạo, trình biên dịch sẽ không thực sự biên dịch các hàm thành viên mẫu; tuy nhiên, trình biên dịch SILL thực hiện việc thay thế Tvào mẫu thành viên KHAI THÁC để các mẫu thành viên này có thể được khởi tạo sau đó. Điểm thất bại này không phải là SFINAE, vì SFINAE chỉ áp dụng khi xác định tập hợp các hàm có thể có cho độ phân giải quá tải , và khởi tạo một lớp không phải là trường hợp xác định một tập hợp các hàm cho độ phân giải quá tải. (Hoặc tôi nghĩ vậy!)
Dan Nissenbaum

93

Tôi đã làm ví dụ ngắn này cũng hoạt động.

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

Bình luận nếu bạn muốn tôi giải thích. Tôi nghĩ rằng mã ít nhiều tự giải thích, nhưng sau đó một lần nữa tôi đã làm nó để tôi có thể sai :)

Bạn có thể thấy nó trong hành động ở đây .


2
Điều này không biên dịch trên VS2012. error C4519: default template arguments are only allowed on a class template.
PythonNut

1
Thật không may. Tôi chỉ thử nghiệm nó với gcc. Có lẽ điều này sẽ giúp: stackoverflow.com/a/17543296/660982
jpihl

1
đây chắc chắn là câu trả lời tốt nhất ở đây và chính xác những gì tôi đang tìm kiếm.
Weipeng L

3
Tại sao cần phải tạo một lớp mẫu khác Q, mặc dù nó bằng T?
ilya1725

1
Bởi vì bạn cần phải tạo mẫu cho testhàm thành viên. Cả hai không thể tồn tại cùng một lúc. Qchỉ chuyển tiếp kiểu mẫu của lớp T. Bạn có thể xóa mẫu lớp Tnhư vậy: cpp.sh/4nxw nhưng điều đó hơi đánh bại mục đích.
jpihl

13

Đối với những người đến muộn đang tìm kiếm một giải pháp "chỉ hoạt động":

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

Biên dịch với:

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

Chạy cho:

./a.out 
11

6
Ừm, tại sao bạn lại đổi tên std::enable_if_tthành resolvedType.
Qwertie

1
Bởi vì không phải ai cũng có thể sử dụng C ++ 17 vì những lý do có thể thay đổi rộng rãi.
James Yang

9

Từ bài này :

Đối số mẫu mặc định không phải là một phần của chữ ký của mẫu

Nhưng người ta có thể làm một cái gì đó như thế này:

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

Nó hoạt động, nhưng về cơ bản, đây là các hàm tạo khuôn mẫu, không phải chính lớp ... Nó không cho phép loại bỏ một trong hai hàm được tạo mẫu giống nhau (khi bạn cần vượt qua quá tải). Tuy nhiên, ý tưởng là tốt đẹp. Bạn có thể viết lại ví dụ OP trong một hình thức làm việc không?
dùng1284631

5

Một cách để giải quyết vấn đề này, chuyên môn hóa các chức năng thành viên là đưa chuyên môn hóa vào một lớp khác, sau đó kế thừa từ lớp đó. Bạn có thể phải thay đổi thứ tự kế thừa để có quyền truy cập vào tất cả các dữ liệu cơ bản khác nhưng kỹ thuật này không hoạt động.

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

Nhược điểm của kỹ thuật này là nếu bạn cần kiểm tra nhiều thứ khác nhau cho các chức năng thành viên khác nhau, bạn sẽ phải tạo một lớp cho mỗi cái và xâu chuỗi nó trong cây kế thừa. Điều này đúng cho việc truy cập các thành viên dữ liệu phổ biến.

Ví dụ:

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

Boolean cần phụ thuộc vào tham số mẫu được suy ra. Vì vậy, một cách dễ dàng để khắc phục là sử dụng tham số boolean mặc định:

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

};

Tuy nhiên, điều này sẽ không hoạt động nếu bạn muốn làm quá tải chức năng thành viên. Thay vào đó, tốt nhất để sử dụng TICK_MEMBER_REQUIREStừ thư viện Tick :

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

};

Bạn cũng có thể triển khai thành viên của riêng mình yêu cầu macro như thế này (chỉ trong trường hợp bạn không muốn sử dụng thư viện khác):

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

Nó không làm việc cho tôi theo cách đó. Maaybe thiếu gì? Bạn có thể viết lại ví dụ OP trong một hình thức làm việc không?
dùng1284631

Ví dụ ban đầu không hoạt động với quá tải. Tôi đã cập nhật câu trả lời của tôi làm thế nào bạn có thể làm điều đó với quá tải.
Paul Fultz II

0

Dưới đây là ví dụ tối giản của tôi, sử dụng macro. Sử dụng dấu ngoặc kép enable_if((...))khi sử dụng các biểu thức phức tạp hơn.

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