C++17stladvanced
std::is_invocable / std::is_invocable_r
C++17 · Practical Modern · Compiler flag: -std=c++17
Explanation
Type traits that check at compile time whether a callable can be invoked with given argument types (`is_invocable`) and additionally whether the return type is convertible to a specified type (`is_invocable_r`). They are essential for writing robust SFINAE-based generic code.
Real-World Scenario
Constrain a callback registration function to only accept callables with the correct signature.
Quick Snippet
snippet.cpp
#include <type_traits>
static_assert(std::is_invocable_v<decltype(std::sqrt), double>);
static_assert(std::is_invocable_r_v<double, decltype(std::sqrt), double>);
template<typename Func>
auto safeCall(Func f, int x) -> std::enable_if_t<std::is_invocable_r_v<int, Func, int>, int> {
return std::invoke(f, x);
}Full Example
is_invocable.cpp
// CodeNebula - C++17 Example: std::is_invocable
// Compile: g++ -std=c++17 -Wall -Wextra -o example is_invocable.cpp
// Demonstrates std::is_invocable and related traits for compile-time
// checking whether a callable can be invoked with given argument types.
// Includes is_invocable_r (with return type check) and is_nothrow_invocable.
#include <iostream>
#include <type_traits>
#include <string>
#include <functional>
int add(int a, int b) { return a + b; }
void greet(const std::string& name) { (void)name; }
int safe_div(int a, int b) noexcept { return b != 0 ? a / b : 0; }
struct Functor {
double operator()(double x) const { return x * 2.0; }
};
struct Widget {
int value;
int get() const { return value; }
};
int main()
{
// ------------------------------------------------------------------
// 1. is_invocable: can we call F with these argument types?
// ------------------------------------------------------------------
std::cout << "=== std::is_invocable ===" << std::endl;
std::cout << " add(int, int) : "
<< std::is_invocable_v<decltype(add), int, int> << std::endl;
std::cout << " add(int) : "
<< std::is_invocable_v<decltype(add), int> << std::endl;
std::cout << " greet(string) : "
<< std::is_invocable_v<decltype(greet), std::string> << std::endl;
std::cout << " Functor(double) : "
<< std::is_invocable_v<Functor, double> << std::endl;
std::cout << " Functor(string) : "
<< std::is_invocable_v<Functor, std::string> << std::endl;
// ------------------------------------------------------------------
// 2. is_invocable_r: can we call F and get return type R?
// ------------------------------------------------------------------
std::cout << "\n=== std::is_invocable_r ===" << std::endl;
std::cout << " add(int,int)->int : "
<< std::is_invocable_r_v<int, decltype(add), int, int> << std::endl;
std::cout << " add(int,int)->double: "
<< std::is_invocable_r_v<double, decltype(add), int, int> << std::endl;
std::cout << " add(int,int)->string: "
<< std::is_invocable_r_v<std::string, decltype(add), int, int> << std::endl;
// ------------------------------------------------------------------
// 3. is_nothrow_invocable: noexcept check
// ------------------------------------------------------------------
std::cout << "\n=== std::is_nothrow_invocable ===" << std::endl;
std::cout << " safe_div(int,int) : "
<< std::is_nothrow_invocable_v<decltype(safe_div), int, int> << std::endl;
std::cout << " add(int,int) : "
<< std::is_nothrow_invocable_v<decltype(add), int, int> << std::endl;
// ------------------------------------------------------------------
// 4. Member function and member data pointers
// ------------------------------------------------------------------
std::cout << "\n=== Member Pointers ===" << std::endl;
std::cout << " Widget::get(Widget) : "
<< std::is_invocable_v<decltype(&Widget::get), Widget> << std::endl;
std::cout << " Widget::value(Widget): "
<< std::is_invocable_v<decltype(&Widget::value), Widget> << std::endl;
// ------------------------------------------------------------------
// 5. Practical: SFINAE-friendly template
// ------------------------------------------------------------------
auto try_call = [](auto&& fn, auto&&... args) {
if constexpr (std::is_invocable_v<decltype(fn), decltype(args)...>) {
std::cout << " Callable -> result: " << fn(args...) << std::endl;
} else {
std::cout << " Not callable with these args" << std::endl;
}
};
std::cout << "\n=== Practical if-constexpr ===" << std::endl;
try_call(add, 5, 10);
try_call(add, "hello", "world");
return 0;
}
/*
Expected output:
=== std::is_invocable ===
add(int, int) : 1
add(int) : 0
greet(string) : 1
Functor(double) : 1
Functor(string) : 0
=== std::is_invocable_r ===
add(int,int)->int : 1
add(int,int)->double: 1
add(int,int)->string: 0
=== std::is_nothrow_invocable ===
safe_div(int,int) : 1
add(int,int) : 0
=== Member Pointers ===
Widget::get(Widget) : 1
Widget::value(Widget): 1
=== Practical if-constexpr ===
Callable -> result: 15
Not callable with these args
*/
Run command
g++ -std=c++17 -Wall -Wextra -o example is_invocable.cpp