CodeNebula
C++20stladvanced

std::invocable / std::regular_invocable (C++20 Addition)

C++20 · The Big Four · Compiler flag: -std=c++20

Explanation

Concept that checks if a callable can be invoked with the given argument types using `std::invoke`. `regular_invocable` adds the semantic requirement that calling it does not modify the callable or its arguments (equality-preserving).

Real-World Scenario

Constrain a generic event system's handler registration to accept only callables with the correct signature.

Quick Snippet

snippet.cpp
#include <concepts>
#include <functional>

template<std::invocable<int, int> F>
int apply_operation(F&& func, int a, int b) {
    return std::invoke(std::forward<F>(func), a, b);
}

auto result = apply_operation(std::plus<>{}, 3, 4);  // 7

Full Example

invocable.cpp
// CodeNebula - C++20 Example: std::invocable Concept
// Compile: g++ -std=c++20 -Wall -Wextra -o example invocable.cpp

// std::invocable<F, Args...> checks that F can be called with Args...
// using std::invoke. It accepts functions, lambdas, function objects,
// and member function pointers.

#include <concepts>
#include <functional>
#include <iostream>
#include <string>
#include <vector>

// Free function
int square(int x) { return x * x; }

// Function object
struct Multiplier {
    int factor;
    int operator()(int x) const { return x * factor; }
};

// Apply a callable to each element and print results
template <typename F, typename T>
    requires std::invocable<F, T>
void apply_and_print(F&& func, const std::vector<T>& items) {
    for (const auto& item : items) {
        std::cout << std::invoke(func, item) << " ";
    }
    std::cout << "\n";
}

// Apply a binary callable
template <typename F, typename T, typename U>
    requires std::invocable<F, T, U>
auto apply_binary(F&& func, T a, U b) {
    return std::invoke(std::forward<F>(func), a, b);
}

// Repeat a no-arg callable N times
template <std::invocable F>
void repeat(F&& func, int n) {
    for (int i = 0; i < n; ++i) {
        std::invoke(func);
    }
}

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5};

    // Apply free function
    std::cout << "Squared: ";
    apply_and_print(square, nums);

    // Apply lambda
    std::cout << "Doubled: ";
    apply_and_print([](int x) { return x * 2; }, nums);

    // Apply function object
    std::cout << "Times 3: ";
    apply_and_print(Multiplier{3}, nums);

    // Binary callable
    auto sum = apply_binary(std::plus<>{}, 10, 20);
    std::cout << "\n10 + 20 = " << sum << "\n";

    auto product = apply_binary([](int a, int b) { return a * b; }, 6, 7);
    std::cout << "6 * 7 = " << product << "\n";

    // Repeat a void callable
    int counter = 0;
    std::cout << "\nCounting: ";
    repeat([&counter]() { std::cout << ++counter << " "; }, 5);
    std::cout << "\n";

    // Concept checks
    std::cout << "\nInvocable checks:\n";
    std::cout << "square(int): " << std::invocable<decltype(square), int> << "\n";
    std::cout << "Multiplier(int): " << std::invocable<Multiplier, int> << "\n";
    std::cout << "plus<>(int,int): " << std::invocable<std::plus<>, int, int> << "\n";
    std::cout << "square(): " << std::invocable<decltype(square)> << "\n";

    return 0;
}

// Expected output:
// Squared: 1 4 9 16 25
// Doubled: 2 4 6 8 10
// Times 3: 3 6 9 12 15
//
// 10 + 20 = 30
// 6 * 7 = 42
//
// Counting: 1 2 3 4 5
//
// Invocable checks:
// square(int): 1
// Multiplier(int): 1
// plus<>(int,int): 1
// square(): 0
Run command
g++ -std=c++20 -Wall -Wextra -o example invocable.cpp