namespace std {
template<class F, class... Args>
concept invocable = requires(F&& f, Args&&... args) {
invoke(std::forward<F>(f), std::forward<Args>(args)...);
};
template<class F, class... Args>
concept regular_invocable = invocable<F, Args...>;
}
概要
invocable
及びregular_invocable
は、任意の関数呼び出し可能な型F
が引数Args...
によって関数呼び出し可能であることを表すコンセプトである。
等しさの保持
invocable
コンセプトではF
のArgs...
による関数呼び出しが等しさを保持することを要求しない。従って、invocable
コンセプトのモデルとなる型F, Args...
は関数呼び出しに際して副作用があっても良く、その出力が内部状態や外部状態に依存していても構わない。
対して、regular_invocable
コンセプトのモデルとなるF
のArgs...
による関数呼び出しには等しさを保持することが要求される。従って、regular_invocable
コンセプトのモデルとなる型F, Args...
は関数呼び出しに際して副作用を持ってはならず、出力は何かしらの状態に依存してはならない。ただし、このことは構文的に(コンパイル時に)チェックされるものではなく、純粋に意味論的な制約として要求・表明される。
例えば、乱数・分布生成器はその呼び出しに際して等しさを保持しない(内部に状態を保ち、出力はそれに依存する)ため、regular_invocable
コンセプトのモデルにはならないがinvocable
コンセプトのモデルとなる。
例
invocable
#include <iostream>
#include <concepts>
#include <random>
template<typename F, typename... Args>
requires std::invocable<F, Args...>
void f(const char* name) {
std::cout << name << " is invocable" << std::endl;
}
template<typename F, typename... Args>
void f(const char* name) {
std::cout << name << " is not invocable" << std::endl;
}
void func(int);
auto lambda = [](auto a) { return a; };
auto mut_lambda = [n = 0](auto a) mutable { ++n; return n + a; };
struct invocable {
template<typename T>
void operator()(T&& t) const {
return t;
}
};
struct not_invocable {};
int main() {
f<decltype(func), int>("func(int)");
f<decltype(lambda), int>("lambda(int)");
f<decltype(lambda), int*>("lambda(int*)");
f<invocable, int>("invocable(int)");
f<invocable, int***>("invocable(int***)");
// 内部状態を保ち、等しさを保持しない呼び出し可能な型
f<decltype(mut_lambda), int>("mut_lambda(int)");
f<std::mt19937>("std::mt19937()");
std::cout << "\n";
f<decltype(func), int*>("func(int*)");
f<not_invocable>("not_invocable()");
f<not_invocable, int>("not_invocable(int)");
}
出力
func(int) is invocable
lambda(int) is invocable
lambda(int*) is invocable
invocable(int) is invocable
invocable(int***) is invocable
mut_lambda(int) is invocable
std::mt19937() is invocable
func(int*) is not invocable
not_invocable() is not invocable
not_invocable(int) is not invocable
regular_invocable
#include <iostream>
#include <concepts>
#include <random>
template<typename F, typename... Args>
requires std::regular_invocable<F, Args...>
void f(const char* name) {
std::cout << name << " is regular_invocable" << std::endl;
}
template<typename F, typename... Args>
void f(const char* name) {
std::cout << name << " is not regular_invocable" << std::endl;
}
void func(int);
auto lambda = [](auto a) { return a; };
auto mut_lambda = [n = 0](auto a) mutable { ++n; return n + a; };
struct invocable {
template<typename T>
void operator()(T&& t) const {
return t;
}
};
struct not_invocable {};
int main() {
f<decltype(func), int>("func(int)");
f<decltype(lambda), int>("lambda(int)");
f<decltype(lambda), int*>("lambda(int*)");
f<invocable, int>("invocable(int)");
f<invocable, int***>("invocable(int***)");
std::cout << "\n";
// 内部状態を保ち、等しさを保持しない呼び出し可能な型
f<decltype(mut_lambda), int>("mut_lambda(int)");
f<std::mt19937>("std::mt19937()");
// これらの型は std::regular_invocable コンセプトのモデルではないが
// C++構文上では std::invocable との差異を区別しない/できないため
// それぞれ「XXX is regular_invocable」と出力される。
std::cout << "\n";
f<decltype(func), int*>("func(int*)");
f<not_invocable>("not_invocable()");
f<not_invocable, int>("not_invocable(int)");
}
出力
func(int) is regular_invocable
lambda(int) is regular_invocable
lambda(int*) is regular_invocable
invocable(int) is regular_invocable
invocable(int***) is regular_invocable
mut_lambda(int) is regular_invocable
std::mt19937() is regular_invocable
func(int*) is not regular_invocable
not_invocable() is not regular_invocable
not_invocable(int) is not regular_invocable
バージョン
言語
- C++20
処理系
- Clang: ??
- GCC: 10.1 ✅
- Visual C++: 2019 Update 3 ✅