في هذه المرحلة ، هذه المكتبة تجريبية وهي فضول نقي. لا يوجد استقرار للواجهة أو جودة التنفيذ. استخدم في مخاطرك.
Dyno يحل مشكلة تعدد الأشكال وقت التشغيل أفضل من الفانيليا C ++. يوفر طريقة لتحديد واجهات يمكن الوفاء بها بشكل غير داخلي ، ويوفر طريقة قابلة للتخصيص بالكامل لتخزين الكائنات المتعددة الأشكال وإرسالها إلى الأساليب الافتراضية. لا يتطلب ذلك الميراث أو تخصيص الكومة أو ترك العالم المريح من دلالات القيمة ، ويمكنه القيام بذلك مع تفوق الفانيليا C ++.
Dyno هو تنفيذ مكتبة لمكتبات لما يعرف أيضًا باسم كائنات سمات الصدأ ، وواجهات GO ، وفئات نوع Haskell ، والمفاهيم الافتراضية. تحت الغطاء ، يستخدم تقنية C ++ المعروفة باسم محو النوع ، وهي الفكرة وراء std::any ، std::function والعديد من الأنواع المفيدة الأخرى.
# include < dyno.hpp >
# include < iostream >
using namespace dyno ::literals ;
// Define the interface of something that can be drawn
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::method< void (std::ostream&) const >
)) { };
// Define how concrete types can fulfill that interface
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = [](T const & self, std::ostream& out) { self. draw (out); }
);
// Define an object that can hold anything that can be drawn.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable> poly_;
};
struct Square {
void draw (std::ostream& out) const { out << " Square " ; }
};
struct Circle {
void draw (std::ostream& out) const { out << " Circle " ; }
};
void f (drawable const & d) {
d. draw (std::cout);
}
int main () {
f (Square{}); // prints Square
f (Circle{}); // prints Circle
}بدلاً من ذلك ، إذا وجدت أن هذا كثير من الغلاية ويمكنك الوقوف باستخدام ماكرو ، فإن ما يلي مكافئ:
# include < dyno.hpp >
# include < iostream >
// Define the interface of something that can be drawn
DYNO_INTERFACE (Drawable,
(draw, void (std::ostream&) const )
);
struct Square {
void draw (std::ostream& out) const { out << " Square " ; }
};
struct Circle {
void draw (std::ostream& out) const { out << " Circle " ; }
};
void f (Drawable const & d) {
d. draw (std::cout);
}
int main () {
f (Square{}); // prints Square
f (Circle{}); // prints Circle
}هذه مكتبة C ++ 17. لن يتم بذل أي جهود لدعم المجمعين الأكبر سناً (آسف). من المعروف أن المكتبة تعمل مع المجمعين التاليين:
| المترجم | إصدار |
|---|---|
| مجلس التعاون الخليجي | > = 7 |
| كلانج | > = 4.0 |
| Apple Clang | > = 9.1 |
تعتمد المكتبة على boost.hana و BOOST.Callabletraits. تعتمد اختبارات الوحدة على libawful والمعايير تعتمد على Google Benchmark و Boost.Typeerasure و mpark.variant ، لكنك لست بحاجة إليها لاستخدام المكتبة. للتطوير المحلي ، يمكن استخدام برنامج dependencies/install.sh لتثبيت جميع التبعيات تلقائيًا.
Dyno هي مكتبة رأس فقط ، لذلك لا يوجد شيء للبناء في كل. فقط أضف include/ الدليل إلى مسار البحث عن رأس المترجم الخاص بك (وتأكد من رضا التبعيات) ، وأنت على ما يرام. ومع ذلك ، هناك اختبارات الوحدة والأمثلة والمعايير التي يمكن بناؤها:
(cd dependencies && ./install.sh) # Install dependencies; will print a path to add to CMAKE_PREFIX_PATH
mkdir build
(cd build && cmake .. -DCMAKE_PREFIX_PATH= " ${PWD} /../dependencies/install " ) # Setup the build directory
cmake --build build --target examples # Build and run the examples
cmake --build build --target tests # Build and run the unit tests
cmake --build build --target check # Does both examples and tests
cmake --build build --target benchmarks # Build and run the benchmarks في البرمجة ، تنشأ الحاجة إلى معالجة الكائنات مع واجهة مشتركة ولكن مع نوع ديناميكي مختلف بشكل متكرر. C ++ يحل هذا بالميراث:
struct Drawable {
virtual void draw (std::ostream& out) const = 0;
};
struct Square : Drawable {
virtual void draw (std::ostream& out) const override final { ... }
};
struct Circle : Drawable {
virtual void draw (std::ostream& out) const override final { ... }
};
void f (Drawable const * drawable) {
drawable-> draw (std::cout);
}ومع ذلك ، فإن هذا النهج له عدة عيوب. إنها
تدخلية
من أجل الوفاء بالواجهة Square Circle ، يحتاج كلاهما إلى الوراثة من فئة القاعدة Drawable Drawable . هذا يتطلب وجود ترخيص لتعديل تلك الفئات ، مما يجعل الميراث غير قابلية للغاية. على سبيل المثال ، كيف يمكنك صنع std::vector<int> الوفاء بالواجهة Drawable ؟ لا يمكنك ببساطة.
غير متوافق مع دلالات القيمة
يتطلب منك الميراث تمرير مؤشرات متعددة الأشكال أو إشارات إلى الأشياء بدلاً من الكائنات نفسها ، والتي تلعب بشكل سيء للغاية مع بقية اللغة والمكتبة القياسية. على سبيل المثال ، كيف يمكنك نسخ متجه Drawable S؟ ستحتاج إلى توفير طريقة clone() ، لكنك الآن قد أفسدت الواجهة الخاصة بك.
مقترن بإحكام مع التخزين الديناميكي
بسبب عدم وجود دلالات القيمة ، عادة ما ينتهي بنا المطاف بتخصيص هذه الأشياء متعددة الأشكال على الكومة. هذا غير فعال بشكل فظيع وخطأ بشكل فظيع ، لأن فرصنا لم نحتاج إلى مدة التخزين الديناميكية على الإطلاق ، وكان كائنًا ذو مدة تخزين تلقائية (على سبيل المثال على المكدس) كافيًا.
يمنع ضم
95 ٪ من الوقت ، ينتهي بنا المطاف باستدعاء طريقة افتراضية من خلال مؤشر أو مرجع متعدد الأشكال. يتطلب ذلك ثلاثة إجماليات: واحدة لتحميل المؤشر إلى VTABLE داخل الكائن ، والآخر لتحميل الإدخال الصحيح في VTABLE ، وواحد للدعوة غير المباشرة إلى مؤشر الوظيفة. كل هذا القفز حوله يجعل من الصعب على المترجم اتخاذ قرارات جيدة. ومع ذلك ، اتضح أنه يمكن تجنب كل هذه الاتجاهات باستثناء المكالمة غير المباشرة.
لسوء الحظ ، هذا هو الخيار الذي اتخذته C ++ بالنسبة لنا ، وهذه هي القواعد التي نلتزم بها عندما نحتاج إلى تعدد الأشكال الديناميكي. أم أنها حقا؟
يحل Dyno مشكلة تعدد الأشكال في وقت التشغيل في C ++ دون أي من العيوب المذكورة أعلاه ، والعديد من الأشياء الجيدة الأخرى. إنها:
غير التداخل
يمكن الوفاء بالواجهة بنوع دون الحاجة إلى أي تعديل لهذا النوع. هيك ، يمكن أن يفي نوع الواجهة نفسها بطرق مختلفة! مع Dyno ، يمكنك تقبيل التسلسلات الهرمية الفئة المضحكة وداعا.
100 ٪ على أساس دلالات القيمة
يمكن تمرير الكائنات المتعددة الأشكال كما هو ، مع دلالات القيمة الطبيعية. هل تحتاج إلى نسخ الأشياء المتعددة الأشكال الخاصة بك؟ بالتأكيد ، فقط تأكد من أن لديهم منشئ نسخ. تريد التأكد من عدم نسخهم؟ بالتأكيد ، وضع علامة عليه كما تم حذفه. مع Dyno ، أساليب clone() السخيفة وانتشار المؤشرات في واجهات برمجة التطبيقات هي أشياء من الماضي.
لا يقترن بأي استراتيجية تخزين محددة
الطريقة التي يتم بها تخزين الكائن متعدد الأشكال هي حقًا تفاصيل تنفيذ ، ويجب ألا تتداخل مع الطريقة التي تستخدم بها هذا الكائن. يمنحك Dyno تحكمًا كاملاً في الطريقة التي يتم بها تخزين كائناتك. لديك الكثير من الأشياء المتعددة الأشكال؟ بالتأكيد ، دعنا نخزنها في المخزن المؤقت المحلي وتجنب أي تخصيص. أو ربما من المنطقي بالنسبة لك تخزين الأشياء على الكومة؟ بالتأكيد ، انطلق.
آلية إرسال مرنة لتحقيق أفضل أداء ممكن
يعد تخزين مؤشر إلى VTABLE مجرد واحدة من العديد من استراتيجيات التنفيذ المختلفة لأداء الإرسال الديناميكي. يمنحك Dyno تحكمًا كاملاً في كيفية حدوث الإرسال الديناميكي ، ويمكن في الواقع التغلب على VTABLES في بعض الحالات. إذا كان لديك وظيفة تم استدعاؤها في حلقة ساخنة ، فيمكنك على سبيل المثال تخزينها مباشرة في الكائن وتخطي عدم التوجيه القابل للتطبيق. يمكنك أيضًا استخدام المعرفة الخاصة بالتطبيق ، حيث لا يمكن أن يضطر برنامج التحويل البرمجي أبدًا إلى تحسين بعض المكالمات الديناميكية-تحديد المستوى على مستوى المكتبة.
أولاً ، تبدأ بتحديد واجهة عامة وإعطائها اسمًا. يوفر Dyno لغة محددة مجال بسيطة للقيام بذلك. على سبيل المثال ، دعونا نحدد واجهة Drawable تصف الأنواع التي يمكن رسمها:
# include < dyno.hpp >
using namespace dyno ::literals ;
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::method< void (std::ostream&) const >
)) { }; هذا يعرّف Drawable على أنه يمثل واجهة لأي شيء يحتوي على طريقة تسمى draw مع مرجع إلى std::ostream . يطلق Dyno هذه الواجهات المفاهيم الديناميكية ، لأنها تصف مجموعات من المتطلبات التي يتعين الوفاء بها من خلال نوع (مثل مفاهيم C ++). ومع ذلك ، على عكس مفاهيم C ++ ، يتم استخدام هذه المفاهيم الديناميكية لإنشاء واجهات وقت التشغيل ، وبالتالي الاسم الديناميكي . التعريف أعلاه يعادل بشكل أساسي ما يلي:
struct Drawable {
virtual void draw (std::ostream&) const = 0;
};بمجرد تحديد الواجهة ، فإن الخطوة التالية هي إنشاء نوع يفي بهذه الواجهة. مع الميراث ، ستكتب شيئًا كهذا:
struct Square : Drawable {
virtual void draw (std::ostream& out) const override final {
out << " square " << std::endl;
}
};مع Dyno ، تعدد الأشكال غير التداخل ، وبدلاً من ذلك يتم توفيره عبر ما يسمى خريطة المفهوم (بعد خرائط مفهوم C ++ 0x):
struct Square { /* ... */ };
template <>
auto const dyno::concept_map<Drawable, Square> = dyno::make_concept_map(
" draw " _s = [](Square const & square, std::ostream& out) {
out << " square " << std::endl;
}
);هذا البناء هو تخصص قالب متغير C ++ 14 المسمى
concept_mapالمحدد في مساحة الاسمdyno::. نهيئ هذا التخصص معdyno::make_concept_map(...).
المعلمة الأولى من Lambda هي *this المعلمة ضمنية ضمنية عندما أعلننا draw كطريقة أعلاه. من الممكن أيضًا مسح الوظائف غير الأعضاء (انظر القسم ذي الصلة).
تحدد خريطة المفهوم كيفية تلبية نوع Square للمفهوم Drawable . بمعنى ما ، فإنه يرسم نوع Square لتنفيذه للمفهوم ، مما يحفز التسمية. عندما يستوفي نوع ما متطلبات المفهوم ، نقول أن النماذج النوعية (أو هي نموذج) هذا المفهوم. الآن بعد أن أصبح Square نموذجًا للمفهوم Drawable ، نود استخدام تعدد Square المربعة كقابلة Drawable . مع الميراث التقليدي ، سنستخدم مؤشرًا إلى فئة أساسية مثل هذا:
void f (Drawable const * d) {
d-> draw (std::cout);
}
f ( new Square{}); مع Dyno ، يمكن تخصيص تعدد الأشكال ودلالات القيمة ، ويمكن تخصيص طريقة تعدد الأشكال حولها. للقيام بذلك ، سنحتاج إلى تحديد نوع يمكنه الاحتفاظ بأي شيء يمكن Drawable . هذا النوع ، بدلاً من Drawable* ، سنقوم بالتجول من وإلى وظائف الأشكال. للمساعدة في تحديد هذا الغلاف ، يوفر Dyno حاوية dyno::poly ، والتي يمكن أن تحمل كائنًا تعسفيًا يفي بمفهوم معين. كما سترى ، dyno::poly له دور مزدوج: إنه يخزن الكائن متعدد الأشكال ويعتني بالإرسال الديناميكي للطرق. كل ما عليك فعله هو كتابة غلاف رفيع على dyno::poly لإعطائه الواجهة المطلوبة بالضبط:
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable> poly_;
};ملاحظة: يمكنك استخدام
dyno::polyمن الناحية الفنية مباشرة في واجهاتك. ومع ذلك ، من المريح استخدام غلاف مع طرق حقيقية منdyno::poly، وبالتالي يوصى بكتابة غلاف.
دعونا نقسم هذا. أولاً ، نحدد عضوًا poly_ وهو عبارة عن حاوية متعددة الأشكال لأي شيء يضعف مفهوم Drawable :
dyno::poly<Drawable> poly_; بعد ذلك ، نحدد مُنشئًا يسمح ببناء هذه الحاوية من النوع التعسفي T :
template < typename T>
drawable (T x) : poly_{x} { } الافتراض غير المدفوع هنا هو أن T في الواقع نماذج مفهوم Drawable . في الواقع ، عندما تقوم بإنشاء dyno::poly من كائن من النوع T ، سوف يذهب Dyno وينظر إلى خريطة المفهوم المحددة لـ Drawable و T ، إن وجدت. إذا لم تكن هناك خريطة مفهوم من هذا القبيل ، فستبلغ المكتبة أننا نحاول إنشاء dyno::poly من نوع لا يدعمه ، ولن يتم تجميع برنامجك.
أخيرًا ، أغرب وأهم جزء من التعريف أعلاه هو طريقة draw :
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); } ما يحدث هنا هو أنه عندما يتم استدعاء .draw على كائننا drawable ، سنقوم بالفعل بإرسال ديناميكي لتنفيذ وظيفة "draw" للكائن المخزّن حاليًا في dyno::poly ، ودعو ذلك. الآن ، لإنشاء وظيفة تقبل أي شيء Drawable ، لا داعي للقلق بشأن المؤشرات والملكية في واجهتك بعد الآن:
void f (drawable d) {
d. draw (std::cout);
}
f (Square{});بالمناسبة ، إذا كنت تفكر في أن هذا كل شيء غبي ويجب أن تستخدم قالبًا ، فأنت على حق. ومع ذلك ، ضع في اعتبارك ما يلي ، حيث تحتاج حقًا إلى تعدد الأشكال في وقت التشغيل :
drawable get_drawable () {
if ( some_user_input ())
return Square{};
else
return Circle{};
}
f (get_drawable()); بالمعنى الدقيق للكلمة ، لا تحتاج إلى لف dyno::poly ، لكن القيام بذلك يضع حاجزًا لطيفًا بين Dyno وبقية الكود الخاص بك ، والذي لا داعي للقلق أبدًا بشأن كيفية تنفيذ طبقة الأشكال المتعددة. أيضا ، تجاهلنا إلى حد كبير كيف تم تنفيذ dyno::poly في التعريف أعلاه. ومع ذلك ، dyno::poly هي حاوية قوية تستند إلى السياسة للكائنات المتعددة الأشكال التي يمكن تخصيصها لاحتياجات الفرد للأداء. يجعل إنشاء غلاف drawable من السهل تعديل استراتيجية التنفيذ التي تستخدمها dyno::poly للأداء دون التأثير على بقية الكود الخاص بك.
الجانب الأول الذي يمكن تخصيصه في dyno::poly هو الطريقة التي يتم بها تخزين الكائن داخل الحاوية. بشكل افتراضي ، نقوم ببساطة بتخزين مؤشر إلى الكائن الفعلي ، مثل الشخص الذي سيفعله تعدد الأشكال القائم على الميراث. ومع ذلك ، فغالبًا ما لا يكون هذا هو التنفيذ الأكثر فعالية ، ولهذا السبب يسمح dyno::poly بتخصيصه. للقيام بذلك ، ما عليك سوى تمرير سياسة التخزين إلى dyno::poly . على سبيل المثال ، دعونا نحدد غلافنا drawable حتى يحاول تخزين الكائنات حتى 16 بايت في المخزن المؤقت المحلي ، ولكن بعد ذلك يعود إلى الكومة إذا كان الكائن أكبر:
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
dyno::poly<Drawable, dyno::sbo_storage< 16 >> poly_;
// ^^^^^^^^^^^^^^^^^^^^^ storage policy
}; لاحظ أنه لم يتغير أي شيء سوى السياسة في تعريفنا. هذا هو مبدأ مهم للغاية من Dynet ؛ هذه السياسات هي تفاصيل التنفيذ ، ويجب ألا تغير طريقة كتابة التعليمات البرمجية الخاصة بك. مع التعريف أعلاه ، يمكنك الآن إنشاء drawable S تمامًا كما فعلت من قبل ، ولن يحدث أي تخصيص عندما يكون الكائن الذي تقوم بإنشائه drawable من نوبات في 16 بايت. عندما لا يكون ذلك مناسبًا ، ستخصص dyno::poly مخزنًا كبيرًا بما يكفي على الكومة.
دعنا نقول أنك في الواقع لا تريد القيام بتخصيص. لا مشكلة ، فقط قم بتغيير السياسة إلى dyno::local_storage<16> . إذا حاولت إنشاء drawable من كائن كبير جدًا بحيث لا يتناسب في التخزين المحلي ، فلن يتم تجميع البرنامج الخاص بك. لا نوفر فقط تخصيصًا ، ولكننا نقوم أيضًا بإنقاذ مؤشر عدم التوجيه في كل مرة نصل فيها إلى الكائن متعدد الأشكال إذا قارنا بالنهج التقليدي القائم على الميراث. من خلال تعديل تفاصيل التنفيذ هذه (المهمة) بالنسبة لك حالة استخدام محددة ، يمكنك جعل البرنامج أكثر كفاءة من الميراث الكلاسيكي.
يتم توفير سياسات تخزين أخرى أيضًا ، مثل dyno::remote_storage و dyno::non_owning_storage . dyno::remote_storage هو واحد افتراضي ، والذي يخزن دائمًا مؤشرًا لكائن مخصص للكومة. dyno::non_owning_storage يخزن مؤشرًا إلى كائن موجود بالفعل ، دون القلق بشأن عمر هذا الكائن. يسمح بتنفيذ طرق عرض متعددة الأشكال غير المملوكة على الكائنات ، وهو أمر مفيد للغاية.
يمكن أيضًا إنشاء سياسات التخزين المخصصة بسهولة تامة. انظر <dyno/storage.hpp> للحصول على التفاصيل.
عندما قدمنا dyno::poly ، ذكرنا أنه كان له دوران ؛ الأول هو تخزين الكائن متعدد الأشكال ، والثاني هو إجراء إرسال ديناميكي. تمامًا مثلما يمكن تخصيص التخزين ، يمكن أيضًا تخصيص طريقة الإرسال الديناميكي باستخدام السياسات. على سبيل المثال ، دعنا نحدد غلافنا drawable بحيث يكون بدلاً من تخزين مؤشر إلى VTABLE ، بدلاً من ذلك يخزن VTABLE في الكائن drawable نفسه. وبهذه الطريقة ، سنتجنب إحدى الاتجاهات في كل مرة نصل فيها إلى وظيفة افتراضية:
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
using Storage = dyno::sbo_storage< 16 >; // storage policy
using VTable = dyno::vtable<dyno::local<dyno::everything>>; // vtable policy
dyno::poly<Drawable, Storage, VTable> poly_;
}; لاحظ أنه لا يوجد شيء إلى جانب سياسة VTable يحتاج إلى تغيير في تعريف نوع drawable لدينا. علاوة على ذلك ، إذا أردنا ذلك ، فيمكننا تغيير سياسة التخزين بشكل مستقل عن سياسة VTABLE. مع ما ورد أعلاه ، على الرغم من أننا نقوم بتوفير جميع عمليات الاتجاه ، فإننا ندفع ثمنها من خلال جعل كائننا drawable أكبر (لأنه يحتاج إلى الاحتفاظ بـ VTABLE محليًا). قد يكون هذا باهظًا إذا كان لدينا العديد من الوظائف في VTABLE. بدلاً من ذلك ، سيكون من المنطقي تخزين معظم VTABLE عن بُعد ، ولكن فقط في هذه الوظائف القليلة التي نسميها بشدة. يجعل Dyno من السهل جدًا القيام بذلك باستخدام المحددات ، والتي يمكن استخدامها لتخصيص الوظائف التي تنطبق عليها السياسة:
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); }
private:
using Storage = dyno::sbo_storage< 16 >;
using VTable = dyno::vtable<
dyno::local<dyno::only<decltype( " draw " _s)>>,
dyno::remote<dyno::everything_else>
>;
dyno::poly<Drawable, Storage, VTable> poly_;
}; بالنظر إلى هذا التعريف ، يتم تقسيم VTable بالفعل إلى قسمين. الجزء الأول محلي للكائن drawable ويحتوي فقط على طريقة draw . الجزء الثاني هو مؤشر إلى vtable في التخزين الثابت الذي يحمل الأساليب المتبقية (المدمر ، على سبيل المثال).
يوفر Dyno سياستين VTable ، dyno::local<> و dyno::remote<> . يجب تخصيص كل من هذه السياسات باستخدام محدد . المختارون المدعومون من المكتبة هم dyno::only<functions...> و dyno::except<...> و dyno::everything_else (والتي يمكن أيضًا تهجئتها dyno::everything ).
عند تحديد مفهوم ما ، غالبًا ما يمكن للمرء أن يوفر تعريفًا افتراضيًا لبعض الوظائف المرتبطة بالمفهوم على الأقل. على سبيل المثال ، بشكل افتراضي ، من المحتمل أن يكون من المنطقي استخدام وظيفة عضو تسمى draw (إن وجدت) لتنفيذ طريقة "draw" المجردة للمفهوم Drawable . لهذا ، يمكن للمرء استخدام dyno::default_concept_map :
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = []( auto const & self, std::ostream& out) { self. draw (out); }
); الآن ، كلما حاولنا أن ننظر إلى كيفية تحقيق نوع T للمفهوم Drawable ، سنعود إلى خريطة المفهوم الافتراضي إذا لم يتم تحديد خريطة مفهوم. على سبيل المثال ، يمكننا إنشاء Circle نوع جديدة:
struct Circle {
void draw (std::ostream& out) const {
out << " circle " << std::endl;
}
};
f (Circle{}); // prints "circle" تعد Circle تلقائيًا نموذجًا Drawable ، على الرغم من أننا لم نحدد صراحة خريطة مفهوم Circle . من ناحية أخرى ، إذا أردنا تحديد خريطة المفاهيم هذه ، فسيكون لها الأسبقية على الافتراضي:
template <>
auto dyno::concept_map<Drawable, Circle> = dyno::make_concept_map(
" draw " _s = [](Circle const & circle, std::ostream& out) {
out << " triangle " << std::endl;
}
);
f (Circle{}); // prints "triangle" من المفيد في بعض الأحيان تحديد خريطة مفهوم لعائلة كاملة من الأنواع في وقت واحد. على سبيل المثال ، قد نرغب في جعل std::vector<T> نموذجًا Drawable ، ولكن فقط عندما يمكن طباعة T إلى دفق. يتم تحقيق ذلك بسهولة باستخدام هذه الخدعة السرية (ليس ذلك):
template < typename T>
auto const dyno::concept_map<Drawable, std::vector<T>, std:: void_t <decltype(
std::cout << std::declval<T>()
)>> = dyno::make_concept_map(
" draw " _s = [](std::vector<T> const & v, std::ostream& out) {
for ( auto const & x : v)
out << x << ' ' ;
}
);
f (std::vector< int >{ 1 , 2 , 3 }) // prints "1 2 3 "لاحظ كيف لا يتعين علينا تعديل
std::vectorعلى الإطلاق. كيف يمكننا أن نفعل هذا مع تعدد الأشكال الكلاسيكية؟ الإجابة: لا يمكن أن تفعل.
يسمح Dyno بمحو الوظائف والوظائف غير الأعضاء التي يتم إرسالها على حجة تعسفية (ولكن حجة واحدة فقط) أيضًا. للقيام بذلك ، ما عليك سوى تحديد المفهوم باستخدام dyno::function بدلاً من dyno::method ، واستخدم العنصر النائب dyno::T للإشارة إلى الوسيطة التي تم محوها:
// Define the interface of something that can be drawn
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::function< void (dyno::T const &, std::ostream&)>
)) { }; يمثل dyno::T const& المعلمة المستخدمة أعلاه نوع الكائن الذي يتم استدعاء الوظيفة عليه. ومع ذلك ، لا يجب أن تكون المعلمة الأولى:
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::function< void (std::ostream&, dyno::T const &)>
)) { };لا يتغير تحقيق المفهوم ما إذا كان المفهوم يستخدم طريقة أو وظيفة ، ولكن تأكد من أن معلمات تنفيذ الوظيفة الخاصة بك تتطابق مع الوظيفة المعلنة في المفهوم:
// Define how concrete types can fulfill that interface
template < typename T>
auto const dyno::default_concept_map<Drawable, T> = dyno::make_concept_map(
" draw " _s = [](std::ostream& out, T const & self) { self. draw (out); }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ matches the concept definition
); أخيرًا ، عند استدعاء function على dyno::poly ، يجب أن تمر في جميع المعلمات بشكل صريح ، نظرًا لأن Dyno لا يمكنه تخمين أي شخص تريد إرساله. يجب أن يتم تمرير المعلمة التي تم إعلانها مع dyno::T في المفهوم dyno::poly نفسها:
// Define an object that can hold anything that can be drawn.
struct drawable {
template < typename T>
drawable (T x) : poly_{x} { }
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out, poly_); }
// ^^^^^ passing the poly explicitly
private:
dyno::poly<Drawable> poly_;
};