最終更新日時(UTC):
が更新

履歴 編集

concept
<concepts>

std::invocable(C++20)

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コンセプトではFArgs...による関数呼び出しが等しさを保持することを要求しない。従って、invocableコンセプトのモデルとなる型F, Args...は関数呼び出しに際して副作用があっても良く、その出力が内部状態や外部状態に依存していても構わない。

対して、regular_invocableコンセプトのモデルとなるFArgs...による関数呼び出しには等しさを保持することが要求される。従って、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

処理系

関連項目

参照