C++20から導入されるコンセプトは、requires節を使用して、型に対してあるメンバ関数の実装を要求することが出来る。 そのメンバ関数に対して、const関数であることを要求できないかと考え、実装してみた。
#include <type_traits> template <typename T> concept Drawable = requires(T x) { // 型がdraw() const:関数を持つことを要求する std::declval<std::add_const_t<T>>().draw(); }; template <Drawable T> void f(T& x) { x.draw(); } struct A { void draw() const {} }; struct B { void draw() {} }; struct C { void func() {} }; int main() { A a; B b; C c; f(a); f(b); // draw()関数が非constなのでコンパイルエラー f(c); // draw()関数をもたないのでコンパイルエラー }
※追記
他の書き方もあるみたい。
// conceptの引数型をconst型にする template <typename T> concept Drawable = requires(const T& x) { { x.draw() }; }; // 非constメンバ関数の実装も同時に制約条件に加えたい場合 // 1. template <typename T> concept ActorClass = requires(const T& x, T& y) { { x.draw() }; { y.update() }; }; // 2. template <typename T> concept ActorClass = requires(const T& x) { { x.draw() }; } and requires(T& x) { { x.update() }; }; // 3. template <typename T> concept ActorClass = requires(const T& x) { { x.draw() }; { const_cast<T&>(x).update() }; };
個人的には、2の書き方が記述量こそ多いが一番直感的かなと思う。が、どれも特に挙動に違いはないと思われるので、好きなのを使えばいい。