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

C++20:如何将成员函数参数化的函数模板转为泛型lambda

问题描述

我有一个以成员函数为模板参数的函数模板,它接收一个对象,会根据该成员函数在对象上的返回值是否满足特定要求来调用不同函数。示例如下:

template<auto member> void callerFunc(S* ps)
{
    if constexpr (requires { { (ps->*member)()->f1() } -> std::same_as<int>; })
        useF1((ps->*member)());
    else
        useF2(ps);
}

这个模板仅在一处使用,因此我希望将其转换为lambda并放入使用它的函数中,但函数参数不依赖模板参数,我不知道如何用lambda实现(或是否可行)。

补充说明:以下代码中,在switch内设置caller并在外部调用十分重要,因为实际代码中switch位于try块,调用操作在对应的catch块中。

完整示例代码:

#include <concepts>
#include <iostream>

struct S1 {
    int f1() { return 1; }
    int f2() { return 2; }
};

struct S2 {
    // 没有f1(),改用f2()
    int f2() { return 2; }
};

struct S {
    S1* getS1() { return &s1; }
    S2* getS2() { return &s2; }

    S1 s1;
    S2 s2;
};

template <typename T> void useF1(T* t) { std::cout << t->f1() << "\n"; }
template <typename T> void useF2(T* t) { std::cout << t->getS2()->f2() << "\n"; }

////////////////////////////////////////////////////////////////////////////////

// 怎么把这个模板移到下面的Thing::foo里?
template<auto member> void callerFunc(S* ps)
{
    if constexpr (requires { { (ps->*member)()->f1() } -> std::same_as<int>; })
        useF1((ps->*member)());
    else
        useF2(ps);
}

struct Thing {
    S s;
    void foo(char c) {
        auto caller = &callerFunc<&S::getS2>; // 必须能在switch外部访问
        switch (c) {
            case 'a': caller = &callerFunc<&S::getS1>; break;
            case 'b': caller = &callerFunc<&S::getS2>; break;
        }
        caller(&s); // 必须在switch外部调用
    }
};

////////////////////////////////////////////////////////////////////////////////

int main() {
    Thing thing;
    thing.foo('a');
    thing.foo('b');
}
解决方案

不同模板实例化的callerFunc是不同类型的函数,直接用函数指针无法统一类型。我们可以通过以下几种方式实现需求:

方法1:用std::function统一类型(简单直接)

把模板逻辑转换成lambda,在switch分支中生成对应成员函数的lambda,用std::function<void(S*)>统一类型,代价是轻微的类型擦除运行时开销:

#include <concepts>
#include <iostream>
#include <functional>

// 保留S1、S2、S、useF1、useF2的定义

struct Thing {
    S s;
    void foo(char c) {
        std::function<void(S*)> caller = [](S* ps) {
            if constexpr (requires { { (ps->*&S::getS2)()->f1() } -> std::same_as<int>; })
                useF1((ps->*&S::getS2)());
            else
                useF2(ps);
        };

        switch (c) {
            case 'a':
                caller = [](S* ps) {
                    if constexpr (requires { { (ps->*&S::getS1)()->f1() } -> std::same_as<int>; })
                        useF1((ps->*&S::getS1)());
                    else
                        useF2(ps);
                };
                break;
            case 'b':
                caller = [](S* ps) {
                    if constexpr (requires { { (ps->*&S::getS2)()->f1() } -> std::same_as<int>; })
                        useF1((ps->*&S::getS2)());
                    else
                        useF2(ps);
                };
                break;
        }

        caller(&s);
    }
};

// main函数不变

方法2:用std::variant避免类型擦除开销

如果想消除std::function的运行时开销,可以用std::variant存储不同类型的lambda,再通过std::visit调用:

#include <concepts>
#include <iostream>
#include <variant>

// 保留S1、S2、S、useF1、useF2的定义

struct Thing {
    S s;
    void foo(char c) {
        auto caller_s2 = [](S* ps) {
            if constexpr (requires { { (ps->*&S::getS2)()->f1() } -> std::same_as<int>; })
                useF1((ps->*&S::getS2)());
            else
                useF2(ps);
        };
        std::variant<decltype(caller_s2), decltype([](S* ps) {
            if constexpr (requires { { (ps->*&S::getS1)()->f1() } -> std::same_as<int>; })
                useF1((ps->*&S::getS1)());
            else
                useF2(ps);
        })> caller = caller_s2;

        switch (c) {
            case 'a':
                caller = [](S* ps) {
                    if constexpr (requires { { (ps->*&S::getS1)()->f1() } -> std::same_as<int>; })
                        useF1((ps->*&S::getS1)());
                    else
                        useF2(ps);
                };
                break;
            case 'b':
                caller = caller_s2;
                break;
        }

        std::visit([&](auto&& func) { func(&s); }, caller);
    }
};

// main函数不变

方法3:封装模板lambda生成器(简洁复用)

写一个模板函数生成对应lambda,既保留原模板的逻辑复用,又把生成逻辑放在函数附近:

#include <concepts>
#include <iostream>
#include <functional>

// 保留S1、S2、S、useF1、useF2的定义

template<auto member>
auto make_caller() {
    return [](S* ps) {
        if constexpr (requires { { (ps->*member)()->f1() } -> std::same_as<int>; })
            useF1((ps->*member)());
        else
            useF2(ps);
    };
}

struct Thing {
    S s;
    void foo(char c) {
        std::function<void(S*)> caller = make_caller<&S::getS2>();

        switch (c) {
            case 'a': caller = make_caller<&S::getS1>(); break;
            case 'b': caller = make_caller<&S::getS2>(); break;
        }

        caller(&s);
    }
};

// main函数不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 19:49:59