ぷろみん

プログラミング的な内容を扱ってます

Concept Lite(軽量コンセプト)で出来る事

概要

gcc6.0にてConcept Liteのコンパイルができるようになったので、色々と触ってみました。
wandboxを利用すれば簡単に試す事ができます。
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

書き方

conceptはconstexprな関数や変数としても解釈されるので以下の書き方は両方同じ意味になります。

template<class T>
concept bool Pointer = requires(T a){
    *a;
};

// 制約を満たす場合はtrueとなる変数templateとしても機能する
bool a = Pointer<int*>;
template<class T>
concept bool Pointer(){
    return requires(T a){
        *a;
    };
}

// 制約を満たす場合はtrueとなる関数templateとしても機能する
bool a = Pointer<int*>();

コンセプト

コンセプトは型として使う事ができます。
型はautoの様に予測され、制約を満たさない場合はエラーとなります。

template<class T>
concept bool Pointer = requires(T a){
    *a;
};

// concept 'Pointer<int>' was not satisfied
Pointer a = 1;

Pointer b = "test"; // OK

関数の引数に制約を加える場合は以下の記述が可能です。

// 直接指定
void F(Pointer){}

// requires-expression
template<class T>
requires Pointer<T>
void F(T) {}

// 型指定の代わりにコンセプトを記述
template<Pointer T>
void F(T){}

// concept-introduction
Pointer{T}
void F(T){}

simple requirement

template適用後の記述内容にエラーが見つかると制約を満たさなくなります。

template<class T>
concept bool Printable = requires(T a){
    std::cout << a;
};

compound requirement

{}で囲まれた演算の結果の型制約を定義できます。

template<class T>
concept bool Compound = requires(T a){
    {a} -> const char*;
    {*a} -> char;
    {int(*a)} -> int;
};

int main(){
    Compound a = "test";
}

type requirement

必要な型を制約に加える事ができます。

template<class T>
concept bool Type = requires(T a){
    typename T::type;
};

struct X{
    using type = int;
};

int main(){
    Type a = X();
}

以下の記述だとコンセプトを満たしません。

concept bool Type = requires(T a){
    T::type;
};

struct X{
    using type = int;
};

int main(){
    // concept 'Type<X>' was not satisfied
    Type a = X();
}

nested requirement

複数のコンセプトを要求する場合に使います。

// 中身が空はエラーなので書いているだけで、0に意味はない
template<class T>
concept bool A = requires(){ 0; };

template<class T>
concept bool B = requires(){ 0; };

template<class T>
concept bool AB = requires(){
    requires A<T>;
    requires B<T>;
};

コンセプトを満たさない型

struct noncopyable {
    noncopyable() = default;
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator=(const noncopyable&) = delete;
};

//単純に!で否定するとコンセプトを満たさない表現が可能
template<class T>
concept bool Noncopyable = !requires(T a){
    a = a;
};

int main(){
    noncopyable a;
    Noncopyable &b = a;
}

継承関係

継承関係での分岐も簡単に書けます。

struct A;
struct B;

template<class T>
concept bool IsA = requires(T a){ static_cast<A*>(&a); };

template<class T>
concept bool IsB = requires(T a){ static_cast<B*>(&a); };

struct A{};
struct B{};
struct AB: public A, B{};

int main(){
    static_assert(IsA<AB>);
    static_assert(IsB<AB>);
}

mixinの型重複の検出

struct A;
struct B;
struct C;

template<class T>
concept bool IsA = requires(T a){
    static_cast<A*>(&a);
    a.AF();
};

template<class T>
concept bool IsB = requires(T a){
    static_cast<B*>(&a);
    a.BF();
};

template<class T>
concept bool IsC = requires(T a){
    static_cast<C*>(&a);
    a.CF();
};

template<class T>
concept bool IsABC = requires(){
    requires IsA<T>;
    requires IsB<T>;
    requires IsC<T>;
};

struct A{ void AF(){} };
struct B{ void BF(){} };
struct C{ void CF(){} };
struct AB: public A, B{};
struct BC: public B, C{};
// ここでAB::BFとBC::BFがb.BF()として呼ばれ曖昧なのでエラーが出るのでコンセプトIsABCを満たさない
struct ABC: public AB, BC{};

// しかしエラーはいつものconcept 'IsABC<ABC>' was not satisfiedなので原因は分かりにくい

int main(){
    IsABC abc = ABC();
}

型となる可能性があるのでメタ関数の命名規則は使わない方が良さそうですね。

余談

何の問題か分かりませんが以下のコードでinternal compiler errorが出ます。

template<class T>
concept bool Compound = requires(T a){
    {++a} -> T&; 
};

int main(){
    Compound a = 0;
}

参考

N3701 concept lite