Pada titik ini, perpustakaan ini bersifat eksperimental dan merupakan keingintahuan murni. Tidak ada stabilitas antarmuka atau kualitas implementasi yang dijamin. Gunakan dengan risiko Anda sendiri.
Dyno memecahkan masalah polimorfisme runtime lebih baik daripada vanilla C ++. Ini menyediakan cara untuk mendefinisikan antarmuka yang dapat dipenuhi secara non-intrusi, dan memberikan cara yang sepenuhnya dapat disesuaikan untuk menyimpan objek polimorfik dan mengirim ke metode virtual. Itu tidak memerlukan warisan, alokasi tumpukan atau meninggalkan dunia semantik nilai yang nyaman, dan itu dapat melakukannya sambil mengungguli vanilla C ++.
Dyno adalah implementasi pustaka murni dari apa yang juga dikenal sebagai objek sifat karat, antarmuka GO, kelas tipe Haskell, dan konsep virtual. Di bawah kap, ia menggunakan teknik C ++ yang dikenal sebagai Tipe Erasure, yang merupakan ide di balik std::any std::function dan banyak tipe berguna lainnya.
# 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
}Atau, jika Anda menemukan ini terlalu banyak boilerplate dan Anda dapat berdiri menggunakan makro, berikut ini setara:
# 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
}Ini adalah perpustakaan C ++ 17. Tidak ada upaya yang akan dilakukan untuk mendukung kompiler yang lebih tua (maaf). Perpustakaan diketahui bekerja dengan kompiler berikut:
| Penyusun | Versi |
|---|---|
| GCC | > = 7 |
| Dentang | > = 4.0 |
| Clang apel | > = 9.1 |
Perpustakaan tergantung pada boost.hana dan boost.callabletraits. Tes unit tergantung pada pencemaran nama baik dan tolok ukur bergantung pada benchmark Google, Boost.typeerasure dan mpark.variant, tetapi Anda tidak perlu mereka menggunakan perpustakaan. Untuk pengembangan lokal, skrip dependencies/install.sh dapat digunakan untuk menginstal semua dependensi secara otomatis.
Dyno adalah perpustakaan header saja, jadi tidak ada yang bisa dibangun. Cukup tambahkan direktori include/ ke jalur pencarian header kompiler Anda (dan pastikan dependensi puas), dan Anda baik untuk pergi. Namun, ada unit tes, contoh dan tolok ukur yang dapat dibangun:
(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 Dalam pemrograman, kebutuhan untuk memanipulasi objek dengan antarmuka umum tetapi dengan tipe dinamis yang berbeda muncul sangat sering. C ++ menyelesaikan ini dengan warisan:
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);
}Namun, pendekatan ini memiliki beberapa kelemahan. Dia
Mengganggu
Agar Square dan Circle untuk memenuhi antarmuka Drawable , mereka berdua perlu mewarisi dari kelas dasar Drawable . Ini membutuhkan lisensi untuk memodifikasi kelas -kelas tersebut, yang membuat warisan sangat tidak dapat dibuktikan. Misalnya, bagaimana Anda membuat std::vector<int> memenuhi antarmuka Drawable ? Anda tidak bisa.
Tidak sesuai dengan semantik nilai
Warisan mengharuskan Anda untuk melewati pointer polimorfik atau referensi ke objek alih -alih objek itu sendiri, yang bermain sangat buruk dengan sisa bahasa dan perpustakaan standar. Misalnya, bagaimana Anda menyalin vektor yang Drawable ? Anda perlu memberikan metode clone() , tetapi sekarang Anda baru saja mengacaukan antarmuka Anda.
Ditambah dengan rapat dengan penyimpanan dinamis
Karena kurangnya semantik nilai, kami biasanya akhirnya mengalokasikan objek polimorfik ini pada tumpukan. Ini sangat tidak efisien dan secara semantik salah, karena kemungkinan kita tidak memerlukan durasi penyimpanan dinamis sama sekali, dan objek dengan durasi penyimpanan otomatis (misalnya pada tumpukan) sudah cukup.
Mencegah inlining
95% dari waktu, kami akhirnya memanggil metode virtual melalui pointer atau referensi polimorfik. Itu membutuhkan tiga indirection: satu untuk memuat pointer ke vtable di dalam objek, satu untuk memuat entri yang tepat di vtable, dan satu untuk panggilan tidak langsung ke pointer fungsi. Semua ini melompat -lompat menyulitkan kompiler untuk membuat keputusan yang baik. Namun, ternyata semua tidak langsung ini kecuali panggilan tidak langsung dapat dihindari.
Sayangnya, ini adalah pilihan yang telah dibuat C ++ untuk kita, dan ini adalah aturan yang kita terikat ketika kita membutuhkan polimorfisme dinamis. Atau benarkah itu?
Dyno memecahkan masalah polimorfisme runtime di C ++ tanpa ada kekurangan yang tercantum di atas, dan lebih banyak lagi barang. Dia:
Tidak mengganggu
Antarmuka dapat dipenuhi oleh jenis tanpa memerlukan modifikasi apa pun untuk jenis itu. Heck, suatu tipe bahkan dapat memenuhi antarmuka yang sama dengan cara yang berbeda! Dengan Dyno , Anda bisa mencium hierarki kelas konyol selamat tinggal.
100% Berdasarkan Nilai Semantik
Objek polimorfik dapat diteruskan apa adanya, dengan semantik nilai alami mereka. Anda perlu menyalin objek polimorfik Anda? Tentu, pastikan mereka memiliki konstruktor copy. Anda ingin memastikan mereka tidak disalin? Tentu, tandai sebagai dihapus. Dengan metode Dyno , konyol clone() dan proliferasi pointer di API adalah hal -hal di masa lalu.
Tidak digabungkan dengan strategi penyimpanan tertentu
Cara objek polimorfik disimpan benar -benar merupakan detail implementasi, dan tidak boleh mengganggu cara Anda menggunakan objek itu. Dyno memberi Anda kendali penuh atas cara objek Anda disimpan. Anda memiliki banyak benda polimorfik kecil? Tentu, mari kita simpan di buffer lokal dan hindari alokasi apa pun. Atau mungkin masuk akal bagi Anda untuk menyimpan barang -barang di tumpukan? Tentu, silakan.
Mekanisme pengiriman fleksibel untuk mencapai kinerja terbaik
Menyimpan pointer ke vtable hanyalah salah satu dari banyak strategi implementasi yang berbeda untuk melakukan pengiriman dinamis. Dyno memberi Anda kendali penuh atas bagaimana pengiriman dinamis terjadi, dan sebenarnya dapat mengalahkan vtables dalam beberapa kasus. Jika Anda memiliki fungsi yang dipanggil dalam loop panas, Anda dapat menyimpannya secara langsung di objek dan melewatkan tidak langsung. Anda juga dapat menggunakan pengetahuan khusus aplikasi yang tidak pernah harus dikompilasi oleh kompiler untuk mengoptimalkan beberapa panggilan dinamis-devirtualisasi tingkat perpustakaan.
Pertama, Anda mulai dengan mendefinisikan antarmuka generik dan memberinya nama. Dyno menyediakan bahasa khusus domain sederhana untuk melakukannya. Misalnya, mari kita tentukan antarmuka Drawable yang menjelaskan jenis yang dapat ditarik:
# include < dyno.hpp >
using namespace dyno ::literals ;
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::method< void (std::ostream&) const >
)) { }; Ini mendefinisikan Drawable sebagai mewakili antarmuka untuk apa pun yang memiliki metode yang disebut draw mengambil referensi ke std::ostream . Dyno menyebut antarmuka ini konsep dinamis , karena mereka menggambarkan set persyaratan yang akan dipenuhi oleh jenis (seperti konsep C ++). Namun, tidak seperti konsep C ++, konsep -konsep dinamis ini digunakan untuk menghasilkan antarmuka runtime, maka namanya dinamis . Definisi di atas pada dasarnya setara dengan yang berikut:
struct Drawable {
virtual void draw (std::ostream&) const = 0;
};Setelah antarmuka didefinisikan, langkah selanjutnya adalah benar -benar membuat jenis yang memenuhi antarmuka ini. Dengan warisan, Anda akan menulis sesuatu seperti ini:
struct Square : Drawable {
virtual void draw (std::ostream& out) const override final {
out << " square " << std::endl;
}
};Dengan dyno , polimorfisme tidak mengganggu dan sebaliknya disediakan melalui apa yang disebut peta konsep (setelah peta konsep 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;
}
);Konstruk ini adalah spesialisasi templat variabel C ++ 14 bernama
concept_mapyang didefinisikan dalamdyno::Namespace. Kami kemudian menginisialisasi spesialisasi itu dengandyno::make_concept_map(...).
Parameter pertama dari lambda adalah parameter implisit *this yang tersirat ketika kami menyatakan draw sebagai metode di atas. Dimungkinkan juga untuk menghapus fungsi non-anggota (lihat bagian yang relevan).
Peta konsep ini mendefinisikan bagaimana tipe Square memenuhi konsep Drawable . Dalam arti tertentu, ia memetakan tipe Square untuk implementasinya konsep, yang memotivasi sebutan. Ketika suatu jenis memenuhi persyaratan suatu konsep, kami mengatakan bahwa model jenis (atau merupakan model) konsep itu. Sekarang Square adalah model konsep Drawable , kami ingin menggunakan Square secara polimorfik sebagai Drawable . Dengan warisan tradisional, kami akan menggunakan pointer ke kelas dasar seperti ini:
void f (Drawable const * d) {
d-> draw (std::cout);
}
f ( new Square{}); Dengan dyno , polimorfisme dan semantik nilai kompatibel, dan cara jenis polimorfik dilewatkan dapat sangat disesuaikan. Untuk melakukan ini, kita perlu mendefinisikan jenis yang dapat menampung apa pun yang bisa Drawable . Ini adalah tipe itu, bukannya Drawable* , yang akan kita lewati ke dan dari fungsi polimorfik. Untuk membantu mendefinisikan pembungkus ini, Dyno menyediakan dyno::poly Container, yang dapat menahan objek sewenang -wenang yang memenuhi konsep yang diberikan. Seperti yang akan Anda lihat, dyno::poly memiliki peran ganda: ia menyimpan objek polimorfik dan mengurus pengiriman metode yang dinamis. Yang perlu Anda lakukan adalah menulis pembungkus tipis di atas dyno::poly untuk memberikannya secara persis antarmuka yang diinginkan:
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_;
};Catatan: Anda dapat secara teknis menggunakan
dyno::polylangsung di antarmuka Anda. Namun, jauh lebih nyaman untuk menggunakan pembungkus dengan metode nyata daripadadyno::poly, dan menulis pembungkus.
Mari kita hancurkan ini. Pertama, kami mendefinisikan poly_ Anggota yang merupakan wadah polimorfik untuk apa pun yang memodelkan konsep Drawable :
dyno::poly<Drawable> poly_; Kemudian, kami mendefinisikan konstruktor yang memungkinkan membangun wadah ini dari tipe T yang sewenang -wenang:
template < typename T>
drawable (T x) : poly_{x} { } Asumsi yang tidak terungkap di sini adalah bahwa T sebenarnya memodelkan konsep Drawable . Memang, ketika Anda membuat dyno::poly dari objek tipe T , dyno akan pergi dan melihat peta konsep yang ditentukan untuk Drawable dan T , jika ada. Jika tidak ada peta konsep seperti itu, perpustakaan akan melaporkan bahwa kami mencoba membuat dyno::poly dari jenis yang tidak mendukungnya, dan program Anda tidak akan dikompilasi.
Akhirnya, bagian yang paling aneh dan terpenting dari definisi di atas adalah metode draw :
void draw (std::ostream& out) const
{ poly_. virtual_ ( " draw " _s)(out); } Apa yang terjadi di sini adalah bahwa ketika .draw dipanggil pada objek drawable kami, kami akan benar -benar melakukan pengiriman dinamis untuk implementasi fungsi "draw" untuk objek yang saat ini disimpan dalam dyno::poly , dan sebut itu. Sekarang, untuk membuat fungsi yang menerima apa pun yang Drawable , tidak perlu khawatir tentang pointer dan kepemilikan di antarmuka Anda lagi:
void f (drawable d) {
d. draw (std::cout);
}
f (Square{});Ngomong -ngomong, jika Anda berpikir bahwa ini semua bodoh dan Anda seharusnya menggunakan templat, Anda benar. Namun, pertimbangkan yang berikut, di mana Anda benar -benar membutuhkan polimorfisme runtime :
drawable get_drawable () {
if ( some_user_input ())
return Square{};
else
return Circle{};
}
f (get_drawable()); Sebenarnya, Anda tidak perlu membungkus dyno::poly , tetapi melakukan hal itu menempatkan penghalang yang bagus antara Dyno dan sisa kode Anda, yang tidak perlu khawatir tentang bagaimana lapisan polimorfik Anda diimplementasikan. Juga, kami sebagian besar mengabaikan bagaimana dyno::poly diimplementasikan dalam definisi di atas. Namun, dyno::poly adalah wadah berbasis kebijakan yang sangat kuat untuk objek polimorfik yang dapat disesuaikan dengan kebutuhan seseorang untuk kinerja. Membuat pembungkus drawable membuatnya mudah untuk mengubah strategi implementasi yang digunakan oleh dyno::poly untuk kinerja tanpa memengaruhi sisa kode Anda.
Aspek pertama yang dapat disesuaikan dalam dyno::poly adalah cara objek disimpan di dalam wadah. Secara default, kami cukup menyimpan pointer ke objek yang sebenarnya, seperti yang akan dilakukan dengan polimorfisme berbasis warisan. Namun, ini seringkali bukan implementasi yang paling efisien, dan itulah sebabnya dyno::poly memungkinkan menyesuaikannya. Untuk melakukannya, cukup berikan kebijakan penyimpanan ke dyno::poly . Misalnya, mari kita tentukan pembungkus drawable kami sehingga mencoba untuk menyimpan objek hingga 16 byte dalam buffer lokal, tetapi kemudian jatuh kembali ke tumpukan jika objek lebih besar:
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
}; Perhatikan bahwa tidak ada kecuali kebijakan yang berubah dalam definisi kami. Itu adalah satu prinsip yang sangat penting dari Dyno ; Kebijakan ini adalah detail implementasi, dan mereka tidak boleh mengubah cara Anda menulis kode Anda. Dengan definisi di atas, Anda sekarang dapat membuat S drawable seperti yang Anda lakukan sebelumnya, dan tidak ada alokasi yang akan terjadi ketika objek yang Anda buat drawable dari Fits dalam 16 byte. Namun, ketika tidak sesuai, dyno::poly akan mengalokasikan buffer yang cukup besar di tumpukan.
Katakanlah Anda sebenarnya tidak pernah ingin melakukan alokasi. Tidak masalah, cukup ubah kebijakan menjadi dyno::local_storage<16> . Jika Anda mencoba membangun drawable dari objek yang terlalu besar untuk dimuat di penyimpanan lokal, program Anda tidak akan dikompilasi. Kami tidak hanya menyimpan alokasi, tetapi kami juga menyimpan tipuan pointer setiap kali kami mengakses objek polimorfik jika kami membandingkan dengan pendekatan berbasis warisan tradisional. Dengan mengubah detail implementasi (penting) ini untuk kasus penggunaan khusus Anda, Anda dapat membuat program Anda jauh lebih efisien daripada dengan warisan klasik.
Kebijakan penyimpanan lainnya juga disediakan, seperti dyno::remote_storage dan dyno::non_owning_storage . dyno::remote_storage adalah yang default, yang selalu menyimpan pointer ke objek yang dialokasikan heap. dyno::non_owning_storage menyimpan pointer ke objek yang sudah ada, tanpa khawatir tentang masa hidup objek itu. Ini memungkinkan penerapan pandangan polimorfik yang tidak dimiliki atas objek, yang sangat berguna.
Kebijakan penyimpanan khusus juga dapat dibuat dengan cukup mudah. Lihat <dyno/storage.hpp> untuk detailnya.
Ketika kami memperkenalkan dyno::poly , kami menyebutkan bahwa ia memiliki dua peran; Yang pertama adalah menyimpan objek polimorfik, dan yang kedua adalah melakukan pengiriman dinamis. Sama seperti penyimpanan dapat disesuaikan, cara pengiriman dinamis dilakukan juga dapat disesuaikan menggunakan kebijakan. Misalnya, mari kita tentukan pembungkus drawable kami sehingga alih -alih menyimpan pointer ke vtable, ia malah menyimpan vtable di objek drawable itu sendiri. Dengan cara ini, kita akan menghindari satu tipuan setiap kali kita mengakses fungsi virtual:
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_;
}; Perhatikan bahwa tidak ada selain kebijakan vtable yang perlu diubah dalam definisi tipe kami drawable . Selain itu, jika kami mau, kami dapat mengubah kebijakan penyimpanan secara independen dari kebijakan vtable. Dengan hal di atas, meskipun kami menyimpan semua ketidakpedulian, kami membayarnya dengan membuat objek drawable lebih besar (karena ia perlu memegang vtable secara lokal). Ini bisa menjadi penghalang jika kami memiliki banyak fungsi di VTable. Sebaliknya, akan lebih masuk akal untuk menyimpan sebagian besar vtable dari jarak jauh, tetapi hanya menyambung beberapa fungsi yang kita sebut berat. Dyno membuatnya sangat mudah untuk melakukannya dengan menggunakan selektor , yang dapat digunakan untuk menyesuaikan fungsi apa yang berlaku kebijakan:
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_;
}; Mengingat definisi ini, VTable sebenarnya terbagi dua. Bagian pertama adalah lokal ke objek drawable dan hanya berisi metode draw . Bagian kedua adalah pointer ke vtable dalam penyimpanan statis yang menampung metode yang tersisa (destruktor, misalnya).
Dyno menyediakan dua kebijakan vtable, dyno::local<> dan dyno::remote<> . Kedua kebijakan ini harus disesuaikan menggunakan pemilih . Para pemilih yang didukung oleh Perpustakaan adalah dyno::only<functions...> , dyno::except<...> , dan dyno::everything_else (yang juga dapat dieja dyno::everything ).
Saat mendefinisikan suatu konsep, sering kali seseorang dapat memberikan definisi default untuk setidaknya beberapa fungsi yang terkait dengan konsep tersebut. Misalnya, secara default, mungkin masuk akal untuk menggunakan fungsi anggota bernama draw (jika ada) untuk mengimplementasikan metode "draw" abstrak dari konsep Drawable . Untuk ini, seseorang dapat menggunakan 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); }
); Sekarang, setiap kali kita mencoba melihat bagaimana beberapa tipe T memenuhi konsep Drawable , kita akan kembali ke peta konsep default jika tidak ada peta konsep yang didefinisikan. Misalnya, kita dapat membuat Circle tipe baru:
struct Circle {
void draw (std::ostream& out) const {
out << " circle " << std::endl;
}
};
f (Circle{}); // prints "circle" Circle secara otomatis merupakan model yang Drawable , meskipun kami tidak secara eksplisit mendefinisikan peta konsep untuk Circle . Di sisi lain, jika kita mendefinisikan peta konsep seperti itu, itu akan diutamakan daripada yang default:
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" Terkadang berguna untuk mendefinisikan peta konsep untuk keluarga jenis yang lengkap sekaligus. Misalnya, kita mungkin ingin membuat std::vector<T> model yang Drawable , tetapi hanya ketika T dapat dicetak ke aliran. Ini mudah dicapai dengan menggunakan trik rahasia ini (tidak demikian):
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 "Perhatikan bagaimana kita tidak perlu memodifikasi
std::vectorsama sekali. Bagaimana kita bisa melakukan ini dengan polimorfisme klasik? Jawaban: Tidak bisa lakukan.
Dyno memungkinkan penghapusan fungsi dan fungsi non-anggota yang dikirim pada argumen sewenang-wenang (tetapi hanya satu argumen) juga. Untuk melakukan ini, cukup tentukan konsep menggunakan dyno::function alih -alih dyno::method , dan gunakan placeholder dyno::T untuk menunjukkan argumen yang dihapus:
// 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& parameter yang digunakan di atas mewakili jenis objek yang diaktifkan fungsi. Namun, itu tidak harus menjadi parameter pertama:
struct Drawable : decltype(dyno::requires_(
" draw " _s = dyno::function< void (std::ostream&, dyno::T const &)>
)) { };Pemenuhan konsep tidak mengubah apakah konsep tersebut menggunakan metode atau fungsi, tetapi pastikan bahwa parameter implementasi fungsi Anda cocok dengan fungsi yang dinyatakan dalam konsep:
// 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
); Akhirnya, ketika memanggil function pada dyno::poly , Anda harus meneruskan semua parameter secara eksplisit, karena Dyno tidak dapat menebak mana yang ingin Anda kirim. Parameter yang dinyatakan dengan dyno::T placeholder dalam konsep harus dilewatkan dyno::poly itu sendiri:
// 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_;
};