You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何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);
}

我已了解该现象的成因,但仍有两个问题待解答:

  1. 为何要如此实现?这种反直觉行为的原因是什么?
  2. 当无法指定精确类型(如本例中的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 08:02:40