เทมเพลต C ++ ที่ยอมรับเฉพาะบางประเภท


158

ใน Java คุณสามารถกำหนดคลาสทั่วไปที่ยอมรับเฉพาะประเภทที่ขยายคลาสที่คุณเลือกเช่น:

public class ObservableList<T extends List> {
  ...
}

ทำได้โดยใช้คำหลัก "ขยาย"

มีบางอย่างที่เทียบเท่ากับคำหลักนี้ใน C ++?


ค่อนข้างเก่าแล้วคำถาม ... ฉันรู้สึกว่าสิ่งที่ขาดหายไปที่นี่ (จากคำตอบ) ก็คือ Java generics ไม่ได้เทียบเท่าเทมเพลตใน C ++ จริงๆ มีความคล้ายคลึงกัน แต่ผู้ที่ควรระมัดระวังในการแปลโซลูชัน java เป็น C ++ โดยตรงเพื่อทราบว่าอาจถูกสร้างขึ้นสำหรับปัญหาประเภทต่างๆ)
idclev 463035818

คำตอบ:


104

ฉันขอแนะนำให้ใช้คุณลักษณะยืนยันแบบคงที่ของ Boost ในคอนเสิร์ตด้วยis_base_ofจากไลบรารี Boost Type Traits:

template<typename T>
class ObservableList {
    BOOST_STATIC_ASSERT((is_base_of<List, T>::value)); //Yes, the double parentheses are needed, otherwise the comma will be seen as macro argument separator
    ...
};

ในบางกรณีที่ง่ายกว่านั้นคุณสามารถส่งต่อแม่แบบส่วนกลางได้ แต่จะกำหนดเฉพาะ (เฉพาะบางส่วนหรืออย่างชัดเจน) สำหรับประเภทที่ถูกต้อง:

template<typename T> class my_template;     // Declare, but don't define

// int is a valid type
template<> class my_template<int> {
    ...
};

// All pointer types are valid
template<typename T> class my_template<T*> {
    ...
};

// All other types are invalid, and will cause linker error messages.

[ไมเนอร์ EDIT 6/12/2556: การใช้เทมเพลตที่ประกาศ แต่ไม่ได้กำหนดจะส่งผลให้ตัวเชื่อมโยงไม่ใช่คอมไพเลอร์และข้อความแสดงข้อผิดพลาด]


การยืนยันแบบคงที่ดีเช่นกัน :)
macbirdie

5
@ John: ฉันกลัวว่าเชี่ยวชาญเฉพาะจะตรงmyBaseTypeเผง ก่อนที่จะปิดการใช้งาน Boost คุณควรทราบว่าส่วนใหญ่เป็นรหัสเทมเพลตสำหรับส่วนหัวเท่านั้นดังนั้นจึงไม่มีหน่วยความจำหรือเวลาที่ต้องใช้งานจริงสำหรับสิ่งที่คุณไม่ได้ใช้ นอกจากนี้สิ่งต่าง ๆ ที่คุณจะใช้ที่นี่ ( BOOST_STATIC_ASSERT()และis_base_of<>) สามารถใช้งานได้โดยการประกาศเท่านั้น(เช่นไม่มีคำจำกัดความที่แท้จริงของฟังก์ชันหรือตัวแปร) ดังนั้นพวกเขาจะไม่ใช้พื้นที่หรือเวลาใด ๆ
j_random_hacker

50
C ++ 11 มาแล้ว static_assert(std::is_base_of<List, T>::value, "T must extend list")ตอนนี้เราสามารถใช้
Siyuan Ren

2
BTW เหตุผลที่จำเป็นต้องใช้วงเล็บคู่คือ BOOST_STATIC_ASSERT เป็นมาโครและวงเล็บพิเศษป้องกันไม่ให้พรีโพรเซสเซอร์ตีความล่าเครื่องหมายจุลภาคภายในอาร์กิวเมนต์ฟังก์ชัน is_base_of เป็นอาร์กิวเมนต์แมโครตัวที่สอง
jfritz42

1
@Andreyua: ฉันไม่เข้าใจสิ่งที่หายไป คุณสามารถลองประกาศตัวแปรmy_template<int> x;หรือmy_template<float**> y;ตรวจสอบว่าคอมไพเลอร์อนุญาตเหล่านี้แล้วประกาศตัวแปรmy_template<char> z;และตรวจสอบว่าไม่
j_random_hacker

134

ซึ่งโดยทั่วไปจะไม่ได้รับการรับรองใน C ++ เนื่องจากมีคำตอบอื่น ๆ ที่ระบุไว้ที่นี่ ใน C ++ เรามักจะนิยามประเภททั่วไปตามข้อ จำกัด อื่นนอกเหนือจาก "การสืบทอดจากคลาสนี้" หากคุณต้องการทำสิ่งนั้นจริง ๆ มันค่อนข้างง่ายที่จะทำใน C ++ 11 และ<type_traits>:

#include <type_traits>

template<typename T>
class observable_list {
    static_assert(std::is_base_of<list, T>::value, "T must inherit from list");
    // code here..
};

สิ่งนี้ทำลายแนวคิดจำนวนมากที่ผู้คนคาดหวังใน C ++ มันเป็นการดีกว่าที่จะใช้ลูกเล่นเช่นการกำหนดลักษณะของคุณ ยกตัวอย่างเช่นอาจจะobservable_listต้องการที่จะยอมรับทุกประเภทของภาชนะที่มี typedefs const_iteratorและbeginและฟังก์ชั่นของสมาชิกที่ผลตอบแทนend const_iteratorหากคุณ จำกัด สิ่งนี้ไว้ในคลาสที่สืบทอดจากlistนั้นผู้ใช้ที่มีประเภทของตัวเองที่ไม่ได้รับมรดกlistแต่ให้ฟังก์ชั่นสมาชิกและ typedefs เหล่านี้จะไม่สามารถใช้งานของคุณobservable_listได้

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

เพื่อความสมบูรณ์การแก้ปัญหาของตัวอย่างด้านบนได้รับ:

#include <type_traits>

template<typename...>
struct void_ {
    using type = void;
};

template<typename... Args>
using Void = typename void_<Args...>::type;

template<typename T, typename = void>
struct has_const_iterator : std::false_type {};

template<typename T>
struct has_const_iterator<T, Void<typename T::const_iterator>> : std::true_type {};

struct has_begin_end_impl {
    template<typename T, typename Begin = decltype(std::declval<const T&>().begin()),
                         typename End   = decltype(std::declval<const T&>().end())>
    static std::true_type test(int);
    template<typename...>
    static std::false_type test(...);
};

template<typename T>
struct has_begin_end : decltype(has_begin_end_impl::test<T>(0)) {};

template<typename T>
class observable_list {
    static_assert(has_const_iterator<T>::value, "Must have a const_iterator typedef");
    static_assert(has_begin_end<T>::value, "Must have begin and end member functions");
    // code here...
};

มีแนวคิดมากมายที่แสดงในตัวอย่างด้านบนที่แสดงคุณลักษณะของ C ++ 11 ข้อความค้นหาบางคำสำหรับคนที่อยากรู้อยากเห็นคือเทมเพลต Variadic, SFINAE, นิพจน์ SFINAE และลักษณะของประเภท


2
ฉันไม่เคยรู้เลยว่าแม่แบบ C ++ ใช้การพิมพ์เป็ดจนถึงวันนี้ ชนิดที่แปลกประหลาด!
Andy

2
เนื่องจากข้อ จำกัด เชิงนโยบายที่กว้างขวางC ++ ได้รับการแนะนำให้รู้จักกับCไม่แน่ใจว่าทำไมtemplate<class T:list>แนวคิดดังกล่าวจึงเป็นความผิด ขอบคุณสำหรับทิป.
bvj

60

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

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

หากคุณต้องการข้อความแสดงข้อผิดพลาดที่ดีกว่า (หรือถ้าคุณต้องการที่จะตรวจจับกรณีที่จะไม่เกิดข้อผิดพลาดของคอมไพเลอร์ แต่ก็ยังไม่สมเหตุสมผล) คุณสามารถทำได้โดยขึ้นอยู่กับความซับซ้อนที่คุณต้องการ ไลบรารี Boost concept_check

ด้วยคอมไพเลอร์ล่าสุดคุณมี built_in static_assertซึ่งสามารถใช้แทนได้


7
ใช่ฉันคิดเสมอว่าเทมเพลตเป็นสิ่งที่ใกล้เคียงที่สุดที่จะพิมพ์เป็ดใน C ++ หากมีองค์ประกอบทั้งหมดที่จำเป็นสำหรับเทมเพลตสามารถใช้ในเทมเพลตได้

@ จอห์น: ฉันขอโทษฉันไม่สามารถทำหัวหรือก้อยที่ ประเภทใดTและรหัสนี้เรียกว่าที่ไหน หากไม่มีบริบทฉันก็ไม่มีโอกาสเข้าใจข้อมูลโค้ดนั้น แต่สิ่งที่ฉันพูดนั้นเป็นความจริง หากคุณพยายามโทรหาtoString()ประเภทที่ไม่มีtoStringฟังก์ชันสมาชิกคุณจะได้รับข้อผิดพลาดในการคอมไพล์
jalf

@ John: ครั้งต่อไปบางทีคุณควรจะเป็นบิตน้อยทริกเกอร์มีความสุขคน downvoting เมื่อปัญหาคือในรหัสของคุณ
jalf

@jalf โอเค +1 นี่เป็นคำตอบที่ยอดเยี่ยมเพียงพยายามทำให้ดีที่สุด ขออภัยที่อ่านผิด ฉันคิดว่าเรากำลังพูดถึงการใช้ type เป็นพารามิเตอร์สำหรับคลาสไม่ใช่สำหรับเท็มเพลตฟังก์ชั่นซึ่งฉันคิดว่าเป็นสมาชิกของอดีต แต่จำเป็นต้องเรียกใช้คอมไพเลอร์เพื่อตั้งค่าสถานะ
John

13

เราสามารถใช้std::is_base_ofและstd::enable_if:
( static_assertสามารถลบออกได้คลาสด้านบนสามารถปรับใช้เองหรือใช้จากการเพิ่มหากเราไม่สามารถอ้างอิงได้type_traits)

#include <type_traits>
#include <list>

class Base {};
class Derived: public Base {};

#if 0   // wrapper
template <class T> class MyClass /* where T:Base */ {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
    typename std::enable_if<std::is_base_of<Base, T>::value, T>::type inner;
};
#elif 0 // base class
template <class T> class MyClass: /* where T:Base */
    protected std::enable_if<std::is_base_of<Base, T>::value, T>::type {
private:
    static_assert(std::is_base_of<Base, T>::value, "T is not derived from Base");
};
#elif 1 // list-of
template <class T> class MyClass /* where T:list<Base> */ {
    static_assert(std::is_base_of<Base, typename T::value_type>::value , "T::value_type is not derived from Base");
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type base; 
    typedef typename std::enable_if<std::is_base_of<Base, typename T::value_type>::value, T>::type::value_type value_type;

};
#endif

int main() {
#if 0   // wrapper or base-class
    MyClass<Derived> derived;
    MyClass<Base> base;
//  error:
    MyClass<int> wrong;
#elif 1 // list-of
    MyClass<std::list<Derived>> derived;
    MyClass<std::list<Base>> base;
//  error:
    MyClass<std::list<int>> wrong;
#endif
//  all of the static_asserts if not commented out
//  or "error: no type named ‘type’ in ‘struct std::enable_if<false, ...>’ pointing to:
//  1. inner
//  2. MyClass
//  3. base + value_type
}

13

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

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


4
แนวคิดได้ถูกทิ้งไปจากมาตรฐานอย่างน่าเสียดาย
macbirdie

4
ข้อ จำกัด และแนวคิดควรนำมาใช้สำหรับ C ++ 20
Petr Javorik

เป็นไปได้แม้ไม่มีแนวคิดการใช้static_assertและ SFINAE ดังที่คำตอบอื่น ๆ แสดง ปัญหาที่เหลือสำหรับใครบางคนที่มาจาก Java หรือ C # หรือ Haskell (... ) คือคอมไพเลอร์ C ++ 20 ไม่ได้ทำการตรวจสอบคำนิยามกับแนวคิดที่จำเป็นซึ่ง Java และ C # ทำ
user7610

10

ฉันคิดว่าคำตอบก่อนหน้านี้หมดไปจากการมองเห็นป่าต้นไม้

generics Java ไม่ได้เช่นเดียวกับแม่ ; พวกเขาใช้ลบออกประเภทซึ่งเป็นเทคนิคแบบไดนามิกมากกว่าpolymorphism รวบรวมเวลาซึ่งเป็นเทคนิคแบบคงที่ มันควรจะชัดเจนว่าทำไมทั้งสองกลยุทธ์ที่แตกต่างกันมากจึงไม่เจลได้ดี

แทนที่จะพยายามใช้โครงสร้างเวลาคอมไพล์เพื่อจำลองเวลารันหนึ่งลองดูสิ่งที่ทำextendsจริง: ตาม Stack OverflowและWikipedia การขยายใช้เพื่อระบุ subclassing

C ++ ยังรองรับ subclassing

นอกจากนี้คุณยังแสดงคลาสคอนเทนเนอร์ซึ่งใช้การลบประเภทในรูปแบบทั่วไปและขยายเพื่อดำเนินการตรวจสอบชนิด ใน C ++ คุณต้องทำเครื่องจักรสำหรับลบประเภทด้วยตัวเองซึ่งง่าย: สร้างตัวชี้ไปที่ซูเปอร์คลาส

ลองห่อมันลงใน typedef เพื่อให้ใช้งานง่ายกว่าทำทั้งคลาสและ voila:

typedef std::list<superclass*> subclasses_of_superclass_only_list;

ตัวอย่างเช่น:

class Shape { };
class Triangle : public Shape { };

typedef std::list<Shape*> only_shapes_list;
only_shapes_list shapes;

shapes.push_back(new Triangle()); // Works, triangle is kind of shape
shapes.push_back(new int(30)); // Error, int's are not shapes

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


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

2
@ DavidLively มันสายไปประมาณสองปีแล้วที่จะวิจารณ์คำตอบนี้เกี่ยวกับมารยาท แต่ฉันก็ไม่เห็นด้วยกับคุณในตัวอย่างนี้ ฉันอธิบายว่าทำไมทั้งสองเทคนิคไม่เข้าด้วยกันก่อนที่จะระบุว่าชัดเจนหลังจาก ฉันให้บริบทและจากนั้นกล่าวว่าข้อสรุปจากบริบทนั้นชัดเจน ไม่พอดีกับแม่พิมพ์ของคุณ
Alice

ผู้เขียนคำตอบนี้กล่าวว่ามีบางอย่างชัดเจนหลังจากทำการยกของหนัก ฉันไม่คิดว่าผู้เขียนตั้งใจจะพูดว่าวิธีแก้ปัญหานั้นชัดเจน
Luke Gehorsam

10

เทียบเท่าที่ยอมรับเฉพาะประเภท T ที่ได้มาจากรายการประเภทดูเหมือน

template<typename T, 
         typename std::enable_if<std::is_base_of<List, T>::value>::type* = nullptr>
class ObservableList
{
    // ...
};

8

บทสรุปผู้บริหาร: อย่าทำอย่างนั้น

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

ข้อ จำกัด ประเภทของ Java เป็นข้อผิดพลาดไม่ใช่คุณสมบัติ พวกเขาอยู่ที่นั่นเพราะ Java จะลบประเภทใน generics ดังนั้น Java ไม่สามารถหาวิธีการเรียกวิธีการตามค่าพารามิเตอร์ประเภทเพียงอย่างเดียว

C ++ ในอีกทางหนึ่งไม่มีข้อ จำกัด ดังกล่าว ประเภทพารามิเตอร์เทมเพลตสามารถเป็นประเภทใดก็ได้ที่เข้ากันได้กับการดำเนินการที่ใช้ด้วย ไม่จำเป็นต้องเป็นคลาสพื้นฐานทั่วไป สิ่งนี้คล้ายกับ "Duck Typing" ของ Python แต่ทำในเวลารวบรวม

ตัวอย่างง่ายๆที่แสดงพลังของเทมเพลต:

// Sum a vector of some type.
// Example:
// int total = sum({1,2,3,4,5});
template <typename T>
T sum(const vector<T>& vec) {
    T total = T();
    for (const T& x : vec) {
        total += x;
    }
    return total;
}

ฟังก์ชันผลรวมนี้สามารถรวมเวกเตอร์ชนิดใดก็ได้ที่สนับสนุนการดำเนินการที่ถูกต้อง มันทำงานร่วมกับทั้งแบบดั้งเดิมเช่น int / long / float / double และประเภทตัวเลขที่ผู้ใช้กำหนดเองซึ่งเกิน + + ผู้ประกอบการ เฮ้คุณสามารถใช้ฟังก์ชั่นนี้เพื่อเข้าร่วมสตริงได้เนื่องจากมันรองรับ + ​​=

ไม่จำเป็นต้องใช้การชกมวย / การถอนการจั่ว

โปรดทราบว่ามันยังสร้างอินสแตนซ์ใหม่ของ T โดยใช้ T () สิ่งนี้เป็นเรื่องเล็กน้อยใน C ++ โดยใช้อินเตอร์เฟสแบบ implicit แต่เป็นไปไม่ได้ใน Java ที่มีข้อ จำกัด ประเภท

แม้ว่าเทมเพลต C ++ จะไม่มีข้อ จำกัด ชนิดชัดเจน แต่ก็ยังคงปลอดภัยและจะไม่คอมไพล์ด้วยรหัสที่ไม่สนับสนุนการดำเนินการที่ถูกต้อง


2
หากคุณแนะนำว่าไม่มีเทมเพลตที่เชี่ยวชาญคุณสามารถอธิบายได้ว่าทำไมมันถึงเป็นภาษา

1
ฉันได้รับจุดของคุณ แต่ถ้าอาร์กิวเมนต์แม่แบบของคุณจะต้องได้รับมาจากประเภทที่เฉพาะเจาะจงแล้วมันจะดีกว่าที่จะมีข้อความที่ง่ายต่อการตีความจาก static_assert กว่าอาเจียนข้อผิดพลาดคอมไพเลอร์ปกติ
jhoffman0x

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

@Curg type specialising มีประโยชน์เมื่อคุณต้องการใช้ประโยชน์จากบางสิ่งที่สามารถทำได้เฉพาะบางประเภทเท่านั้น ตัวอย่างเช่นบูลีนคือ ~ ปกติ ~ หนึ่งไบต์ต่อหนึ่งถึงแม้ว่าหนึ่งไบต์สามารถ ~ ปกติ ~ ถือ 8 บิต / บูลีน; คลาสคอลเลกชันเทมเพลตสามารถ (และในกรณีของ std :: map ไม่) มีความเชี่ยวชาญสำหรับบูลีนดังนั้นจึงสามารถแพ็คข้อมูลได้แน่นขึ้นเพื่อประหยัดหน่วยความจำ
thecoshman

นอกจากนี้เพื่อชี้แจงคำตอบนี้ไม่ได้พูดว่า "ไม่เคยเชี่ยวชาญเทมเพลต" มันบอกว่าอย่าใช้คุณสมบัตินั้นเพื่อพยายาม จำกัด ประเภทที่สามารถใช้ได้กับเทมเพลต
thecoshman

6

นั่นเป็นไปไม่ได้ใน C ++ ธรรมดา แต่คุณสามารถตรวจสอบพารามิเตอร์แม่แบบที่รวบรวมเวลาผ่านการตรวจสอบแนวคิดเช่นใช้BCCL ของ Boost

ตั้งแต่ C ++ 20 แนวคิดได้กลายเป็นคุณสมบัติที่เป็นทางการของภาษา


2
ดีก็เป็นไปได้ แต่การตรวจสอบแนวคิดก็ยังคงเป็นความคิดที่ดี :)
j_random_hacker

ฉันหมายถึงว่ามันเป็นไปไม่ได้ใน C ++ "ธรรมดา" ;)
macbirdie

5
class Base
{
    struct FooSecurity{};
};

template<class Type>
class Foo
{
    typename Type::FooSecurity If_You_Are_Reading_This_You_Tried_To_Create_An_Instance_Of_Foo_For_An_Invalid_Type;
};

ตรวจสอบให้แน่ใจว่าคลาสที่ได้รับสืบทอดมาโครงสร้าง FooSecurity และคอมไพเลอร์จะหงุดหงิดในที่ที่เหมาะสมทั้งหมด


@Zehelvion Type::FooSecurityใช้ในคลาสเทมเพลต หากคลาสที่ส่งผ่านในอาร์กิวเมนต์แม่แบบไม่ได้FooSecurityพยายามที่จะใช้มันทำให้เกิดข้อผิดพลาด มันเป็นที่แน่ใจว่าถ้าระดับผ่านในอาร์กิวเมนต์แม่แบบไม่ได้ FooSecurity Baseมันไม่ได้มาจาก
GingerPlusPlus

2

การใช้แนวคิด C ++ 20

https://en.cppreference.com/w/cpp/language/constraints cppreference ให้กรณีการใช้การสืบทอดเป็นตัวอย่างแนวคิดที่ชัดเจน:

template <class T, class U>
concept Derived = std::is_base_of<U, T>::value;
 
template<Derived<Base> T>
void f(T);  // T is constrained by Derived<T, Base>

สำหรับหลายฐานฉันคาดเดาไวยากรณ์จะเป็น:

template <class T, class U, class V>
concept Derived = std::is_base_of<U, T>::value || std::is_base_of<V, T>::value;
 
template<Derived<Base1, Base2> T>
void f(T);

GCC 10 ดูเหมือนจะได้ดำเนินการมันhttps://gcc.gnu.org/gcc-10/changes.htmlและคุณจะได้รับมันเป็น PPA บน Ubuntu 20.04 https://godbolt.org/ GCC ท้องถิ่นของฉันยังไม่รู้จักconceptดังนั้นจึงไม่แน่ใจว่าเกิดอะไรขึ้น


1

มีบางอย่างที่เทียบเท่ากับคำหลักนี้ใน C ++?

เลขที่

ขึ้นอยู่กับสิ่งที่คุณพยายามที่จะบรรลุอาจมีทดแทนที่เพียงพอ (หรือดีกว่า)

ฉันดูรหัส STL (บน linux ฉันคิดว่าเป็นรหัสที่ได้มาจากการใช้งานของ SGI) มันมี "การยืนยันแนวคิด"; ตัวอย่างเช่นหากคุณต้องการประเภทที่เข้าใจ*xและ++xการยืนยันแนวคิดจะมีรหัสนั้นในฟังก์ชั่นไม่ทำอะไรเลย (หรือสิ่งที่คล้ายกัน) #ifdef debugมันไม่จำเป็นต้องค่าใช้จ่ายบางอย่างเพื่อให้มันอาจจะเป็นสมาร์ทที่จะนำมันในแมโครที่มีความหมายขึ้นอยู่กับ

หากความสัมพันธ์คลาสย่อยเป็นสิ่งที่คุณต้องการทราบจริงๆคุณสามารถยืนยันใน Constructor ที่T instanceof list(ยกเว้นว่า "สะกด" แตกต่างกันใน C ++) ด้วยวิธีนี้คุณสามารถทดสอบวิธีออกจากคอมไพเลอร์ที่ไม่สามารถตรวจสอบให้คุณได้


1

ไม่มีคำหลักสำหรับการตรวจสอบประเภทดังกล่าว แต่คุณสามารถใส่รหัสบางอย่างที่จะล้มเหลวอย่างเป็นระเบียบ:

(1) ถ้าคุณต้องการให้เท็มเพลตฟังก์ชั่นยอมรับเฉพาะพารามิเตอร์ของคลาสฐาน X ที่แน่นอนให้กำหนดให้กับการอ้างอิง X ในฟังก์ชันของคุณ (2) ถ้าคุณต้องการยอมรับฟังก์ชั่น แต่ไม่ใช่แบบดั้งเดิมหรือกลับกันหรือคุณต้องการกรองคลาสในวิธีอื่นให้เรียกใช้ฟังก์ชันตัวช่วยเทมเพลต (ว่าง) ภายในฟังก์ชันที่กำหนดไว้สำหรับคลาสที่คุณต้องการยอมรับเท่านั้น

คุณสามารถใช้ (1) และ (2) ในฟังก์ชันสมาชิกของคลาสเพื่อบังคับให้ตรวจสอบประเภทเหล่านี้ในคลาสทั้งหมด

คุณอาจจะใส่มันลงไปใน Macro ที่ชาญฉลาดเพื่อบรรเทาความเจ็บปวดของคุณ :)


-2

คุณสามารถสร้างเทมเพลตของคุณอ่านดังนี้:

template<typename T>
class ObservableList {
  std::list<T> contained_data;
};

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

ด้วยความรู้ที่ดีที่สุดของฉันสิ่งก่อสร้างที่จะสะท้อนถึงคำสั่ง Java ในขอบเขตที่สมบูรณ์ไม่มีอยู่ในมาตรฐานปัจจุบัน

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

ใน C ++ 11 การแนะนำแนวคิดควรทำให้ง่ายขึ้น แต่ฉันไม่คิดว่ามันจะทำสิ่งที่คุณต้องการอย่างแน่นอน

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