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

如何实现适配实参值类别(拷贝/移动语义)的准确可调用性概念?

如何实现适配实参值类别(拷贝/移动语义)的准确可调用性概念?

你的困惑完全合理,std::invocable的行为确实容易让人误解——它的检查逻辑是“能否用std::declval<Args>()生成的实参调用目标”,而不是“能否用任意值类别的Args类型实参调用”。我们先拆解问题的根源,再给出符合你需求的解决方案。

为什么std::invocable会有这样的行为?

std::invocable<F, Args...>的核心逻辑依赖于std::declval<T>()生成实参,这个函数的返回规则是:

  • 如果T是对象类型(非引用),返回T&&(右值引用);
  • 如果T是引用类型,直接返回T(左值/右值引用)。

对应你的例子来看:

  • std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>>为true,因为std::declval<std::unique_ptr<int>>()生成的是右值引用,函数参数是值类型,可以通过移动构造接收这个右值(unique_ptr支持移动);
  • 但你实际传左值p1时,会尝试拷贝构造函数参数,而unique_ptr禁用了拷贝,所以编译失败——这就是你觉得“不符合预期”的原因:std::invocable默认检查的是右值实参的调用可行性,而非左值。

你的测试代码格式化后如下:

#include <concepts>
#include <memory>

// 你原本预期这行应该成立,但std::invocable返回true
// static_assert(!std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>>);
static_assert(!std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>&>);
static_assert(std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>&&>);

void foo(std::unique_ptr<int>) {}

int main() {
    [[maybe_unused]] std::unique_ptr<int> p1 = std::unique_ptr<int>(new int);
    [[maybe_unused]] std::unique_ptr<int>& r1 = p1;

    // 以下两行编译失败,因为unique_ptr不可拷贝
    // foo(p1);
    // foo(r1);
    foo(std::unique_ptr<int>(new int)); // 右值可以正常调用
}

如何实现适配拷贝/移动语义的可调用性概念?

你需要的是根据实参的实际值类别来检查调用可行性,而不是默认用右值。可以通过两种方式实现:

1. 直接复用std::invocable,显式指定实参的引用类型

不需要自定义概念,只要给std::invocable传递对应值类别的实参类型即可:

  • 检查左值实参的调用可行性:用T&作为实参类型
    static_assert(!std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>&>);
    
    这会检查“能否用左值引用的unique_ptr调用函数”,结果为false,完全符合你的预期。
  • 检查右值实参的调用可行性:用T或T&&作为实参类型
    static_assert(std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>>);
    static_assert(std::invocable<void(std::unique_ptr<int>), std::unique_ptr<int>&&>);
    
  • 检查任意值类别的实参都能调用:同时验证左值和右值
    template <typename F, typename... Args>
    concept invocable_with_any_value_category = 
        std::invocable<F, Args&...> && std::invocable<F, Args&&...>;
    
    比如对于void(const std::unique_ptr<int>&),这个概念会返回true,因为左值和右值都能绑定到const左值引用。

2. 自定义语义更直观的概念

如果你想封装更易读的概念,比如专门检查左值/右值实参的调用可行性:

#include <concepts>
#include <memory>
#include <utility>

// 检查:能否用Args类型的左值实参调用F
template <typename F, typename... Args>
concept invocable_with_lvalues = requires {
    std::invoke(std::declval<F>(), std::declval<Args&>()...);
};

// 检查:能否用Args类型的右值实参调用F
template <typename F, typename... Args>
concept invocable_with_rvalues = requires {
    std::invoke(std::declval<F>(), std::declval<Args&&>()...);
};

// 检查:能否用任意值类别的Args类型实参调用F
template <typename F, typename... Args>
concept invocable_with_any_value_category = 
    invocable_with_lvalues<F, Args...> && invocable_with_rvalues<F, Args...>;

用你的foo函数验证:

void foo(std::unique_ptr<int>) {}

// 符合预期:左值实参无法调用(不能拷贝)
static_assert(!invocable_with_lvalues<decltype(foo), std::unique_ptr<int>>);
// 符合预期:右值实参可以调用(可以移动)
static_assert(invocable_with_rvalues<decltype(foo), std::unique_ptr<int>>);
// 符合预期:不是所有值类别都能调用
static_assert(!invocable_with_any_value_category<decltype(foo), std::unique_ptr<int>>);

总结

std::invocable本身没有“错误”,只是它的默认行为是检查右值实参的调用可行性。要适配拷贝/移动语义,只需要:

  • 明确你要检查的是左值还是右值实参;
  • 要么给std::invocable传递对应的引用类型实参,要么自定义封装好的概念。

这样就能准确反映拷贝/移动语义下的调用可行性了。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.07 10:48:09