2011-08-07 43 views
99

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 this answer, tôi nghĩ 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.std :: enable_if để có điều kiện biên dịch chức năng thành viên

Thật không may, những điều sau đây không biên dịch với gcc 4.7 và sau giờ làm việc và giờ cố gắng, tôi 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 không g ++ xóa instantiation sai cho chức năng thành viên thứ hai? Theo tiêu chuẩn, std::enable_if< bool, T = void >::type chỉ 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 đề mà g ++ không xóa chức năng thành viên thứ hai và tin rằng đây phải là một tình trạng quá tải.

+1

Tôi không chắc chắn, nhưng tôi nghĩ rằng đó là những điều sau đây: enable_if dựa trên SFINAE (lỗi thay thế không phải là một lỗi). Tuy nhiên, bạn không có bất kỳ sự thay thế nào ở đây, vì không có tham số nào không thể được sử dụng để xác định mức sử dụng quá tải nào. Bạn nên làm cho "true" und "false" 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 nó có lẽ quá đơn giản bây giờ ...) – Philipp

+2

Tôi nghĩ về điều đó quá và cố gắng sử dụng 'std :: is_same < T, int > :: giá trị' và'! std :: is_same < T, int > :: giá trị' cho kết quả tương tự. – evnu

Trả lời

78

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

I thought of that too and tried to use std::is_same< T, int >::value and ! std::is_same< T, int >::value which gives the same result.

Đó là vì khi mẫu lớp được khởi tạo (mà sẽ xảy ra khi bạn tạo một đối tượng kiểu Y<int> trong các trường hợp khác), nó instantiates tất cả các tờ khai thành viên (không nhất thiết là họ định nghĩa/cơ quan!). Trong số đó cũng là các mẫu thành viên của nó. Lưu ý rằng sau đó, T được biết và !std::is_same< T, int >::value sản lượng sai. Vì vậy, nó sẽ tạo ra một lớp Y<int> 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>::type truy cập một loại không tồn tại, vì vậy tuyên bố rằng là vô hình thành. 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 tùy thuộc vào tham số của mẫu thành viên. Sau đó, các khai báo hợp lệ, bởi vì toàn bộ kiểu vẫn còn phụ thuộc. Khi bạn cố gắng gọi một trong số chúng, trích dẫn đối số cho các đối số mẫu của chúng xảy ra và SFINAE xảy ra như mong đợi. Xem this question và câu trả lời tương ứng về cách thực hiện điều đó.

+11

... Chỉ cần làm rõ, trong trường hợp nó hữu ích: Khi một thể hiện của lớp mẫu 'Y' đượ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 S perform thực hiện việc thay thế 'T' thành các mẫu thành viên DECLARATIONS sao cho 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 chức năng có thể 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 chức năng cho độ phân giải quá tải. (Hoặc vì vậy tôi nghĩ!) –

5

Một cách để giải quyết vấn đề này, chuyên môn hoá các hàm thành viên là đưa chuyên môn 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ững bất lợi của kỹ thuật này là nếu bạn cần phải thử nghiệm rất 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 thực hiện một lớp cho mỗi một, và chuỗi nó trong cây thừa kế. Điều này đúng cho việc truy cập các thành viên dữ liệu chung.

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

tôi làm ví dụ ngắn này mà cũng làm việc.

#include <iostream> 
#include <type_traits> 

class foo; 
class bar; 

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

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

int main() 
{ 
    check<foo> check_foo; 
    check<bar> check_bar; 
    if (!check_foo.test() && check_bar.test()) 
     std::cout << "It works!" << std::endl; 

    return 0; 
} 

Nhận xét nếu bạn muốn tôi xây dựng.Tôi nghĩ rằng mã này ít nhiều tự giải thích, nhưng sau đó tôi lại tạo ra nó để tôi có thể sai :)

Bạn có thể thấy nó hoạt động here.

+2

Điều này không biên dịch trên VS2012. 'lỗi C4519: đối số mẫu mặc định chỉ được phép trên mẫu lớp'. – 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: http://stackoverflow.com/a/17543296/660982 – jpihl

+1

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

3

Yêu cầu boolean phụ thuộc vào tham số mẫu được suy luận. Vì vậy, một cách dễ dàng để khắc phục là sử dụng thông 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 quá tải chức năng thành viên. Thay vào đó, hết sức mình để sử dụng TICK_MEMBER_REQUIRES từ Tick thư viện:

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ể thực hiện thành viên của riêng bạn đòi hỏi vĩ mô 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 
+0

Nó không hoạt động cho tôi theo cách đó. Maaybe cái gì đó là mất tích? Bạn có thể viết lại ví dụ OP trong một biểu mẫu làm việc không? – user1284631

+0

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 như thế nào bạn có thể làm điều đó với quá tải. –

6

Từ this bài:

Default template arguments are not part of the signature of a template

Nhưng ai có thể làm điều 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 
} 
+0

Nó hoạt động, nhưng đây là cơ bản các chức năng templating, không phải là lớp chính nó ... Nó không cho phép thả một trong hai chức năng giống hệt prototyped không (khi bạn cần phải 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 biểu mẫu làm việc không? – user1284631

+0

'class std :: enable_if .....' phải là 'typename std :: enable_if .....' – marcinj

8

Đối với những người cuối comers rằng đang tìm kiếm một giải pháp mà "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; 
} 

Compile với:

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

Chạy cho:

./a.out 
11 
Các vấn đề liên quan