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

如何实现类std::invoke行为的C++ LIFT宏?

问题描述

我希望实现一个类std::invoke行为的LIFT宏。目前已实现两种LIFT宏:

  • 一种可将普通函数转为函数对象,代码如下:
#define LIFT(F) 
    [&](auto&&... args) 
    noexcept(noexcept((F)(std::forward<decltype(args)>(args)...))) 
    -> decltype((F)(std::forward<decltype(args)>(args)...)) { 
        return (F)(std::forward<decltype(args)>(args)...); 
    }

可使用LIFT(std::max)作为函数对象。

  • 另一种用于调用第一个参数的成员函数,代码如下:
#define LIFT_MEMFN(F) 
    [&](auto&& self, auto&&... args) 
    noexcept(noexcept(std::forward<decltype(self)>(self).F(std::forward<decltype(args)>(args)...))) 
    -> decltype(std::forward<decltype(self)>(self).F(std::forward<decltype(args)>(args)...)) { 
        return std::forward<decltype(self)>(self).F(std::forward<decltype(args)>(args)...); 
    }

我想通过SFINAE技巧实现一个类std::invoke行为的LIFT宏:当传入第一个参数a0时,优先尝试a0.F(args...),再尝试a0->F(args...),最后回退到F(a0, args...)。请问这是否可行?

可行方案

完全可行,我们可以利用C的重载决议优先级结合SFINAE(或C20的requires表达式)来实现这一逻辑,最终的LIFT宏可以自动适配成员函数(对象调用、指针调用)和普通函数的场景,完全对齐std::invoke的行为。

实现思路

  1. 优先级排序:通过constexpr if的分支顺序,让编译器优先检查并选择合法的调用形式:
    • 第一优先级:直接调用对象的成员函数a0.F(args...)
    • 第二优先级:调用指针的成员函数a0->F(args...)
    • 第三优先级:普通函数调用F(a0, args...)
  2. 完美转发:用std::forward保留参数的左值/右值特性,同时转发原函数的noexcept属性和返回值类型。
  3. 简洁性:使用C++20的requires表达式替代传统SFINAE代码,逻辑更直观。

最终实现代码

#include <utility>

#define LIFT(F) \
    [&]<typename... Args>(Args&&... args) noexcept( \
        noexcept( \
            []<typename... Ts>(Ts&&... ts) -> decltype(auto) { \
                if constexpr (requires { std::forward<Ts>(ts).*F(std::forward<Ts>(ts)...); }) { \
                    return std::forward<Ts>(ts).*F(std::forward<Ts>(ts)...); \
                } else if constexpr (requires { std::forward<Ts>(ts)->*F(std::forward<Ts>(ts)...); }) { \
                    return std::forward<Ts>(ts)->*F(std::forward<Ts>(ts)...); \
                } else { \
                    return F(std::forward<Ts>(ts)...); \
                } \
            }(std::forward<Args>(args)...) \
        ) \
    ) -> decltype(auto) { \
        if constexpr (requires { std::forward<Args>(args).*F(std::forward<Args>(args)...); }) { \
            return std::forward<Args>(args).*F(std::forward<Args>(args)...); \
        } else if constexpr (requires { std::forward<Args>(args)->*F(std::forward<Args>(args)...); }) { \
            return std::forward<Args>(args)->*F(std::forward<Args>(args)...); \
        } else { \
            return F(std::forward<Args>(args)...); \
        } \
    }

关键细节说明

  • requires表达式:直接判断某类调用是否合法,替代了传统的decltype+std::enable_if组合,代码可读性更强。
  • decltype(auto):自动推导返回值类型,保留原函数的引用/值特性(比如原函数返回左值引用,生成的函数对象也会返回左值引用)。
  • noexcept转发:通过内部lambda的noexcept推导,确保生成的函数对象的异常属性与原调用完全一致。
  • 优先级保证:constexpr if的分支顺序直接决定了调用优先级,编译器会按顺序检查,选择第一个合法的调用形式。

使用示例

#include <iostream>
#include <algorithm>

struct Foo {
    int add(int a, int b) const noexcept { return a + b; }
};

int multiply(int a, int b) noexcept { return a * b; }

int main() {
    Foo f;
    Foo* pf = &f;

    // 调用成员函数(对象形式)
    std::cout << LIFT(&Foo::add)(f, 3, 4) << std::endl; // 输出7
    // 调用成员函数(指针形式)
    std::cout << LIFT(&Foo::add)(pf, 5, 6) << std::endl; // 输出11
    // 调用普通函数
    std::cout << LIFT(multiply)(2, 3) << std::endl; // 输出6
    // 调用标准库函数
    std::cout << LIFT(std::max)(10, 20) << std::endl; // 输出20
}

内容的提问来源于stack exchange,提问作者Ben

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 19:52:53