Hạn chế tham số mẫu C ++ cho lớp con


80

Làm cách nào để buộc một tham số mẫu Tlà một lớp con của một lớp cụ thể Baseclass? Một cái gì đó như thế này:

template <class T : Baseclass> void function(){
    T *object = new T();

}

3
Bạn đang cố gắng đạt được điều gì bằng cách làm điều này?
sth

2
Tôi chỉ muốn đảm bảo rằng T thực sự là một thể hiện của một lớp con hoặc chính lớp đó. Mã bên trong hàm mà tôi đã cung cấp khá nhiều không liên quan.
phant0m

6
trái lại, nó rất phù hợp. Nó quyết định việc đưa công việc vào bài kiểm tra đó là một ý tưởng hay hay không. Trong nhiều trường hợp (tất cả?), Bạn hoàn toàn không cần phải tự mình thực thi các ràng buộc đó, mà hãy để trình biên dịch thực hiện khi khởi tạo. Ví dụ: đối với câu trả lời được chấp nhận, sẽ tốt hơn nếu bạn kiểm tra xem có Tnguồn gốc từ Baseclass. Hiện tại, kiểm tra đó là ẩn và không hiển thị đối với độ phân giải quá tải. Nhưng nếu không có nơi nào một ràng buộc ngầm như vậy được thực hiện, thì dường như không có lý do gì cho một hạn chế giả tạo.
Johannes Schaub - litb

1
Vâng tôi đồng ý. Tuy nhiên, tôi chỉ muốn biết liệu có cách nào để đạt được điều này hay không :) Nhưng tất nhiên, bạn có một điểm rất hợp lệ và cảm ơn vì sự sáng suốt.
phant0m

Câu trả lời:


53

Trong trường hợp này, bạn có thể làm:

template <class T> void function(){
    Baseclass *object = new T();

}

Điều này sẽ không biên dịch nếu T không phải là một lớp con của Baseclass (hoặc T Baseclass).


à vâng, đó là một ý kiến ​​hay. cảm ơn! Tôi hiểu rồi thì không có cách nào xác định nó trong định nghĩa mẫu?
phant0m

2
@ phant0m: Đúng. Bạn không thể ràng buộc rõ ràng các tham số mẫu (ngoại trừ việc sử dụng các khái niệm, được xem xét cho c ++ 0x nhưng sau đó bị loại bỏ). Tất cả các ràng buộc diễn ra ngầm bởi các thao tác bạn thực hiện trên nó (hay nói cách khác ràng buộc duy nhất là "Kiểu phải hỗ trợ tất cả các thao tác được thực hiện trên nó").
sepp2k

1
à ic. Cảm ơn rất nhiều vì đã làm rõ!
phant0m

8
Điều đó thực thi phương thức khởi tạo T () và yêu cầu sự tồn tại của phương thức khởi tạo T (). Xem câu trả lời của tôi để biết cách tránh những yêu cầu đó.
Douglas Leeder

3
Đẹp và rõ ràng, nhưng đây là một vấn đề nếu T thuộc lớp “nặng ký”.
3Dave,

84

Với trình biên dịch tuân thủ C ++ 11, bạn có thể làm như sau:

template<class Derived> class MyClass {

    MyClass() {
        // Compile-time sanity check
        static_assert(std::is_base_of<BaseClass, Derived>::value, "Derived not derived from BaseClass");

        // Do other construction related stuff...
        ...
   }
}

Tôi đã thử nghiệm điều này bằng cách sử dụng trình biên dịch gcc 4.8.1 bên trong môi trường CYGWIN - vì vậy nó cũng sẽ hoạt động trong môi trường * nix.


Đối với tôi nó cũng hoạt động như thế này: template<class TEntity> class BaseBiz { static_assert(std::is_base_of<BaseEntity, TEntity>::value, "TEntity not derived from BaseEntity");...
Matthias Dieter Wallnöfer

1
Tôi nghĩ đây là câu trả lời dễ đọc nhất mà tránh được mã thừa trong thời gian chạy.
Kyle

50

Để thực thi mã ít vô ích hơn trong thời gian chạy, bạn có thể xem tại: http://www.stroustrup.com/bs_faq2.html#constraints cung cấp một số lớp thực hiện kiểm tra thời gian biên dịch hiệu quả và tạo ra các thông báo lỗi đẹp hơn.

Đặc biệt:

template<class T, class B> struct Derived_from {
        static void constraints(T* p) { B* pb = p; }
        Derived_from() { void(*p)(T*) = constraints; }
};

template<class T> void function() {
    Derived_from<T,Baseclass>();
}

2
Đối với tôi, đây là câu trả lời hay nhất và thú vị nhất. Hãy chắc chắn kiểm tra Câu hỏi thường gặp của Stroustrup để đọc thêm về tất cả các loại ràng buộc bạn có thể thực thi tương tự như điều này.
Jean-Philippe Pellet

1
Thật vậy, đây là một trong những câu trả lời! Cảm ơn. Trang web được đề cập đã được chuyển đến đây: stroustrup.com/bs_faq2.html#constraints
Jan Korous

Đây là một câu trả lời tuyệt vời. Có bất kỳ cách tốt để tránh các cảnh báo unused variable 'p'unused variable 'pb'?
Filip S.

@FilipS. thêm (void)pb;vào sau B* pb = p;.
bit2shift

11

Bạn không cần khái niệm, nhưng bạn có thể sử dụng SFINAE:

template <typename T>
boost::enable_if< boost::is_base_of<Base,T>::value >::type function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Lưu ý rằng điều này sẽ khởi tạo hàm chỉ khi điều kiện được đáp ứng, nhưng nó sẽ không cung cấp lỗi hợp lý nếu điều kiện không được đáp ứng.


Điều gì sẽ xảy ra nếu bạn gói tất cả các chức năng như thế này? btw nó trả về cái gì?
the_drow

Các enable_ifdiễn một loại thứ hai tham số mặc định void. Biểu thức enable_if< true, int >::typeđại diện cho loại int. Tôi thực sự không thể hiểu câu hỏi đầu tiên của bạn là gì, bạn có thể sử dụng SFINAE cho bất cứ điều gì bạn muốn, nhưng tôi không hiểu bạn định làm gì với điều này trên tất cả các chức năng.
David Rodríguez - dribeas

7

Vì C ++ 11 bạn không cần Boost hoặc static_assert. C ++ 11 giới thiệu is_base_ofenable_if. C ++ 14 giới thiệu kiểu tiện lợi enable_if_t, nhưng nếu bạn gặp khó khăn với C ++ 11, bạn có thể đơn giản sử dụng enable_if::typethay thế.

Phương án 1

Giải pháp của David Rodríguez có thể được viết lại như sau:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of<Base, T>::value, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Phương án 2

Kể từ C ++ 17, chúng tôi có is_base_of_v. Giải pháp có thể được viết lại thành:

#include <type_traits>

using namespace std;

template <typename T>
enable_if_t<is_base_of_v<Base, T>, void> function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Phương án 3

Bạn cũng có thể chỉ giới hạn toàn bộ mẫu. Bạn có thể sử dụng phương pháp này để xác định toàn bộ các lớp. Lưu ý cách tham số thứ hai của enable_if_tđã bị loại bỏ (trước đó nó đã được đặt thành void). Giá trị mặc định của nó thực sự là void, nhưng nó không quan trọng, vì chúng tôi không sử dụng nó.

#include <type_traits>

using namespace std;

template <typename T,
          typename = enable_if_t<is_base_of_v<Base, T>>>
void function() {
   // This function will only be considered by the compiler if
   // T actualy derived from Base
}

Từ tài liệu về tham số mẫu, chúng ta thấy đó typename = enable_if_t...là tham số mẫu có tên trống. Chúng tôi chỉ đơn giản sử dụng nó để đảm bảo rằng định nghĩa của một kiểu tồn tại. Đặc biệt, enable_if_tsẽ không được xác định nếu Basekhông phải là cơ sở của T.

Kỹ thuật trên được đưa ra như một ví dụ trong enable_if.


Sẽ thật tuyệt nếu có thể viết Phương án 3 như sau phải không? template <class T : Base>
Macsinus

4

Bạn có thể sử dụng Boost Concept Check 's BOOST_CONCEPT_REQUIRES:

#include <boost/concept_check.hpp>
#include <boost/concept/requires.hpp>

template <class T>
BOOST_CONCEPT_REQUIRES(
    ((boost::Convertible<T, BaseClass>)),
(void)) function()
{
    //...
}

0

Bằng cách gọi các hàm bên trong mẫu của bạn tồn tại trong lớp cơ sở.

Nếu bạn thử và khởi tạo mẫu của mình bằng loại không có quyền truy cập vào chức năng này, bạn sẽ nhận được lỗi thời gian biên dịch.


3
Điều này không đảm bảo rằng đó T là một BaseClass vì các thành viên được khai báo trong BaseClasscó thể được lặp lại trong khai báo của T.
Daniel Trebbien
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.