網(wǎng)站建設(shè)主要內(nèi)容包括登封網(wǎng)絡(luò)推廣
文章目錄
- 一、引言
- 二、`std::is_invocable` 概述
- 代碼示例
- 輸出結(jié)果
- 三、`std::is_invocable` 的工作原理
- 簡化實(shí)現(xiàn)示例
- 四、`std::is_invocable` 的相關(guān)變體
- 1. `std::is_invocable_r`
- 2. `std::is_nothrow_invocable` 和 `std::is_nothrow_invocable_r`
- 五、使用場景
- 1. 模板元編程
- 2. 泛型算法
- 六、注意事項(xiàng)
- 七、結(jié)論
一、引言
在現(xiàn)代 C++ 編程中,我們經(jīng)常會編寫一些通用的代碼,這些代碼需要處理不同類型的可調(diào)用對象(如函數(shù)、函數(shù)指針、成員函數(shù)指針、lambda 表達(dá)式等)。在使用這些可調(diào)用對象之前,我們可能需要在編譯時就確定它們是否可以以特定的參數(shù)列表進(jìn)行調(diào)用。C++17 引入的 std::is_invocable
系列類型特征就為我們提供了這樣的能力,它允許我們在編譯時進(jìn)行調(diào)用可行性的檢查,從而增強(qiáng)代碼的健壯性和通用性。
二、std::is_invocable
概述
std::is_invocable
是定義在 <type_traits>
頭文件中的一個模板元函數(shù)。它用于在編譯時檢查一個可調(diào)用對象是否可以使用給定的參數(shù)類型進(jìn)行調(diào)用。std::is_invocable
有多個重載形式,基本形式如下:
template< class F, class... Args >
struct is_invocable;template< class F, class... Args >
inline constexpr bool is_invocable_v = is_invocable<F, Args...>::value;
代碼示例
#include <iostream>
#include <type_traits>// 普通函數(shù)
void foo(int x) {std::cout << "foo called with " << x << std::endl;
}int main() {std::cout << std::boolalpha;// 檢查 foo 是否可以用 int 類型參數(shù)調(diào)用std::cout << "Is foo invocable with int? " << std::is_invocable_v<decltype(foo), int> << std::endl;// 檢查 foo 是否可以用 double 類型參數(shù)調(diào)用(隱式轉(zhuǎn)換可行)std::cout << "Is foo invocable with double? " << std::is_invocable_v<decltype(foo), double> << std::endl;return 0;
}
輸出結(jié)果
Is foo invocable with int? true
Is foo invocable with double? true
在上述代碼中,我們定義了一個普通函數(shù) foo
,它接受一個 int
類型的參數(shù)。然后使用 std::is_invocable_v
檢查 foo
是否可以用 int
和 double
類型的參數(shù)調(diào)用。由于 double
可以隱式轉(zhuǎn)換為 int
,所以兩種檢查結(jié)果都為 true
。
三、std::is_invocable
的工作原理
std::is_invocable
的實(shí)現(xiàn)基于 SFINAE(Substitution Failure Is Not An Error)原則。當(dāng)我們使用 std::is_invocable<F, Args...>
時,編譯器會嘗試在編譯時構(gòu)造一個對可調(diào)用對象 F
的調(diào)用,參數(shù)類型為 Args...
。如果這個調(diào)用是合法的,那么 std::is_invocable<F, Args...>::value
將為 true
;否則,它將為 false
。
簡化實(shí)現(xiàn)示例
#include <type_traits>// 輔助模板,用于檢測調(diào)用是否可行
template <typename F, typename... Args, typename = void>
struct is_invocable_helper : std::false_type {};template <typename F, typename... Args>
struct is_invocable_helper<F, Args..., std::void_t<decltype(std::declval<F>()(std::declval<Args>()...))>>: std::true_type {};// 定義 is_invocable
template <typename F, typename... Args>
struct is_invocable : is_invocable_helper<F, Args...> {};// 輔助模板,用于打印結(jié)果
template <typename F, typename... Args>
void print_is_invocable() {std::cout << "Is callable with given args? " << is_invocable<F, Args...>::value << std::endl;
}// 普通函數(shù)
void bar(int x) {}int main() {std::cout << std::boolalpha;print_is_invocable<decltype(bar), int>();return 0;
}
在這個示例中,我們定義了一個輔助模板 is_invocable_helper
,它使用 std::void_t
和 decltype
來檢測對可調(diào)用對象 F
的調(diào)用是否合法。如果合法,is_invocable_helper
將繼承自 std::true_type
;否則,它將繼承自 std::false_type
。
四、std::is_invocable
的相關(guān)變體
1. std::is_invocable_r
std::is_invocable_r
用于檢查一個可調(diào)用對象是否可以使用給定的參數(shù)類型進(jìn)行調(diào)用,并且返回值可以隱式轉(zhuǎn)換為指定的類型。
#include <iostream>
#include <type_traits>int add(int a, int b) {return a + b;
}int main() {std::cout << std::boolalpha;// 檢查 add 是否可以用 int, int 調(diào)用并返回 intstd::cout << "Is add invocable with int, int and return int? " << std::is_invocable_r_v<int, decltype(add), int, int> << std::endl;// 檢查 add 是否可以用 int, int 調(diào)用并返回 doublestd::cout << "Is add invocable with int, int and return double? " << std::is_invocable_r_v<double, decltype(add), int, int> << std::endl;return 0;
}
2. std::is_nothrow_invocable
和 std::is_nothrow_invocable_r
std::is_nothrow_invocable
檢查一個可調(diào)用對象是否可以使用給定的參數(shù)類型進(jìn)行調(diào)用,并且調(diào)用過程不會拋出異常。std::is_nothrow_invocable_r
則在此基礎(chǔ)上還要求返回值可以隱式轉(zhuǎn)換為指定的類型。
#include <iostream>
#include <type_traits>// 不拋出異常的函數(shù)
void safe_foo(int x) noexcept {std::cout << "safe_foo called with " << x << std::endl;
}int main() {std::cout << std::boolalpha;// 檢查 safe_foo 是否可以用 int 調(diào)用且不拋出異常std::cout << "Is safe_foo nothrow invocable with int? " << std::is_nothrow_invocable_v<decltype(safe_foo), int> << std::endl;return 0;
}
五、使用場景
1. 模板元編程
在模板元編程中,我們經(jīng)常需要根據(jù)可調(diào)用對象的調(diào)用可行性來選擇不同的實(shí)現(xiàn)路徑。
#include <iostream>
#include <type_traits>template <typename F, typename... Args, std::enable_if_t<std::is_invocable_v<F, Args...>, int> = 0>
auto call_if_invocable(F&& f, Args&&... args) {return std::forward<F>(f)(std::forward<Args>(args)...);
}template <typename F, typename... Args, std::enable_if_t<!std::is_invocable_v<F, Args...>, int> = 0>
void call_if_invocable(F&&, Args&&...) {std::cout << "Not invocable." << std::endl;
}void baz(int x) {std::cout << "baz called with " << x << std::endl;
}int main() {call_if_invocable(baz, 42);call_if_invocable([](double) {}, 10); // 這里不匹配調(diào)用,輸出 Not invocable.return 0;
}
2. 泛型算法
在編寫泛型算法時,我們可以使用 std::is_invocable
來確保傳入的可調(diào)用對象符合算法的要求。
#include <iostream>
#include <vector>
#include <type_traits>template <typename Container, typename Func, std::enable_if_t<std::is_invocable_v<Func, typename Container::value_type>, int> = 0>
void apply(Container& c, Func f) {for (auto& elem : c) {f(elem);}
}int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};auto print = [](int x) { std::cout << x << " "; };apply(numbers, print);std::cout << std::endl;return 0;
}
六、注意事項(xiàng)
- 隱式轉(zhuǎn)換:
std::is_invocable
會考慮參數(shù)的隱式轉(zhuǎn)換。例如,如果一個函數(shù)接受int
類型的參數(shù),那么傳入short
或char
類型的參數(shù)也會被認(rèn)為是可調(diào)用的,因?yàn)榇嬖陔[式轉(zhuǎn)換。 - 成員函數(shù)指針:在使用成員函數(shù)指針時,需要注意傳遞合適的對象實(shí)例作為第一個參數(shù)。例如,對于一個成員函數(shù)
void MyClass::func()
,調(diào)用時需要傳遞MyClass
的實(shí)例或指針。
#include <iostream>
#include <type_traits>class MyClass {
public:void member_func() {std::cout << "Member function called." << std::endl;}
};int main() {std::cout << std::boolalpha;// 檢查成員函數(shù)指針是否可調(diào)用std::cout << "Is member_func invocable? " << std::is_invocable_v<decltype(&MyClass::member_func), MyClass&> << std::endl;return 0;
}
七、結(jié)論
std::is_invocable
系列類型特征為 C++ 程序員提供了強(qiáng)大的編譯時檢查能力,使得我們可以在編寫通用代碼時更加安全和高效。通過合理使用 std::is_invocable
及其變體,我們可以避免在運(yùn)行時出現(xiàn)調(diào)用錯誤,提高代碼的健壯性和可維護(hù)性。同時,在模板元編程和泛型算法中,std::is_invocable
也發(fā)揮著重要的作用。