为何std::is_invocable_r_v对void返回类型的表现不符合预期?
关于
std::is_invocable_r_v<void, TFun>的疑问 在以下代码中,当我在std::is_invocable_r_v中使用非void的返回类型(例如float)时,可以区分两种函数类型,但使用void时却无法正常工作:
#include <type_traits> #include <iostream> void process(auto fn) { if constexpr (std::is_invocable_r_v<float, decltype(fn)>) { std::cout << "float function call (works just fine!)" << std::endl; [[maybe_unused]] auto res = fn(); } else { std::cout << "void function call" << std::endl; fn(); } if constexpr (std::is_invocable_r_v<void, decltype(fn)>) { std::cout << "void function call" << std::endl; fn(); } else { std::cout << "float function call (never works!)" << std::endl; [[maybe_unused]] auto res = fn(); } } void f1() { } float f2() { return 0.0f; } int main() { std::cout << "Call for function void f1()" << std::endl; process(f1); std::cout << "Call for function float f2()" << std::endl; process(f2); }
我已了解该现象的成因,但仍有两个问题待解答:
- 为何要如此实现?这种反直觉行为的原因是什么?
- 当无法指定精确类型(如本例中的
float)时,最佳的解决方案是什么?
问题1:为何std::is_invocable_r_v<void, TFun>会有这种行为?
这种设计是为了匹配C中忽略返回值的常规语义:在C里,你可以直接调用任何有返回值的函数而不使用它的返回值,这完全合法。std::is_invocable_r<void, F, Args...>的设计就是模拟这种场景——它检查的是“能否调用函数并丢弃返回值”,而不是“函数的返回值是否严格等于void”。
举个例子,你可以写f2();(调用返回float的函数却不接收返回值),这在C++里是允许的,所以std::is_invocable_r_v<void, decltype(f2)>会返回true。这种设计让is_invocable_r的语义更贴合实际代码的写法,而不是做严格的类型匹配。
问题2:无法指定精确类型时的最佳解决方案
如果你需要严格区分“返回void的函数”和“返回非void的函数”,而不是判断能否忽略返回值,最稳健的方案是直接检查函数的返回类型是否为void,而不是用is_invocable_r。
方案1:自定义类型 trait
可以自己实现一个trait来判断可调用对象的返回类型是否严格为void:
#include <type_traits> #include <utility> template<typename F, typename... Args> struct returns_void { static constexpr bool value = std::is_void_v<std::invoke_result_t<F, Args...>>; }; template<typename F, typename... Args> constexpr bool returns_void_v = returns_void<F, Args...>::value;
然后在代码中使用:
void process(auto fn) { if constexpr (!returns_void_v<decltype(fn)>) { std::cout << "non-void function call" << std::endl; [[maybe_unused]] auto res = fn(); } else { std::cout << "void function call" << std::endl; fn(); } }
方案2:直接用std::invoke_result_t判断
如果不需要封装,也可以直接在if constexpr里写:
if constexpr (std::is_void_v<std::invoke_result_t<decltype(fn)>>) { // 处理void返回的函数 } else { // 处理非void返回的函数 }
这两种方案都是严格检查返回类型是否为void,完全符合需求,而且是标准库支持的写法,兼容性和稳健性都很高。
内容的提问来源于stack exchange,提问作者Damir Tenishev
相关产品推荐
相关产品推荐

