如何检查可调用对象是否具有特定的签名,包括参数和返回类型?

问题描述 投票:0回答:1

我有两个函数系列,它们以两种不同的方式更新变量:

  1. 调用一些函数来进行一些更改,然后由客户端代码应用这些更改(下面代码中的
    calculate
    );
  2. 其他人“直接”通过指针更改值(下面代码中的
    update
    )。

在调用这些系列的范围时,我想统一与这些系列的工作(为了简单起见,我在这里省略了范围的使用,只传递一个值)。

一切都工作得很好,直到客户端代码中的开发人员错误地将参数“保留”在 lambda 中,误导了我的

process
函数。

查看代码

#include <type_traits>
#include <iostream>

void process(float& value, auto fn) {
    if constexpr (std::is_invocable_r_v<float, decltype(fn)>) {
        std::cout << "Add a change" << std::endl;
        value += fn();
    }
    else {
        std::cout << "Update the value" << std::endl;
        fn(value);
    }
}

void update(float*v)
{
    *v += 1.0f;
}

float calculate()
{
    return 1.0f;
}

int main() {
    float value = 0.0f;

    // Process by adding a change
    process(value, []() { return calculate(); });

    // Process by updating the value
    process(value, [](auto& v) { update(&v); });

    // User mistakenly kept parameter 'auto&' and used lambda with calculate call,
    // so the result will be lost in void process(...), since it will apply "Update the value" branch
    process(value, [](auto&) { return calculate(); });
}

作为最后一个流程调用的结果,使用 lambda 调用

process
,该 lambda 接受一个参数,因此导致
fn(value)
调用并丢失
calculate
结果。

问题是,我是否可以在

process
中进行某种检查,以确保如果某些可调用对象作为
fn
传递,采用
float*
类型的参数,则它不能返回值(返回类型应该是void)这样就不会丢失?

任何类型的编译时错误或断言都可以。

我似乎应该以某种方式玩这个

std::is_invocable_r_v<float, decltype(fn)
,包括参数的类型,但我不知道怎么做。

当然,我可以编写一个包装器,将所有

calculate
函数带到
update
函数,我想这更容易、更安全,但是,如果没有这样的包装器,这可以完成吗?

c++ templates generics std
1个回答
0
投票

@Igor Tandetnik 已经给出了一个适用于所提供示例的示例。您可以将

process()
函数修改为以下内容:

void process(float& value, auto fn) {
    if constexpr (std::is_invocable_r_v<float, decltype(fn)>) {
        std::cout << "Add a change" << std::endl;
        value += fn();
    }
    else if constexpr (std::is_same_v<void, decltype(fn(value))>) {
        std::cout << "Update the value" << std::endl;
        fn(value);
    }
    else {
        static_assert(false);
    }
}

这是一个非常快速且简单的解决方案。但是,如果您想将时间浪费在(几乎)无用的替代方案上,那么您可以:

如果

fn
看起来像:

[](float v) { update(&v); })

更新分支将按预期执行,但它不会更新传递给它的值,而只是更新参数的本地副本。

如果您想更好地控制更新中要执行的内容以及添加分支中的内容,从而减少错误,您可以使用以下内容(我从here获得了帮助:

#include <concepts>
#include <type_traits>
#include <tuple>

namespace FT {
    template <typename T>
    struct FunctionTraits;

    template <typename R, typename... Params>
    struct FunctionTraits<R(*)(Params...)> {
        using Ret = R;

        using Arity = std::integral_constant<std::size_t, sizeof...(Params)>;

        template <std::size_t i>
        struct Args {
            using type = std::tuple_element_t<i, std::tuple<Params...>>;
        };
    };

    template <typename C, typename R, typename... Params>
    struct FunctionTraits<R(C::*)(Params...) const> {
        using Ret = R;

        using Arity = std::integral_constant<std::size_t, sizeof...(Params)>;

        template <std::size_t i>
        struct Args {
            using type = std::tuple_element_t<i, std::tuple<Params...>>;
        };
    };

    template <typename T>
    struct FunctionTraits : FunctionTraits<decltype(&T::operator())> {};

    template <typename T>
        requires std::is_function_v<T>
    struct FunctionTraits<T> : FunctionTraits<decltype(&std::declval<T>())> {};


    template <typename T, typename F>
    concept Returns = std::is_same_v<T, typename FunctionTraits<F>::Ret>;

    template <typename F>
    constexpr std::size_t Arity = typename FunctionTraits<F>::Arity();

    template <std::size_t i, typename F>
    using ArgTypeAt = typename FunctionTraits<F>::template Args<i>::type;
}
template <typename F>
concept IsAdder = FT::Returns<float, F> && FT::Arity<F> == 0;

template <typename F>
concept IsUpdater = FT::Returns<void, F> && FT::Arity<F> == 1 && std::is_same_v<float&, FT::ArgTypeAt<0, F>>;


void process(float& value, auto fn) {
    if constexpr (IsAdder<decltype(fn)>) {
        std::cout << "Add a change" << std::endl;
        value += fn();
    }
    else if constexpr (IsUpdater<decltype(fn)>) {
        std::cout << "Update the value" << std::endl;
        fn(value);
    }
    else {
        static_assert(false);
    }
}

这也强制(这不是我最初的意图)您不要使用

auto
作为传递给 lambda 的参数。相反,您必须直接使用
float

实例

© www.soinside.com 2019 - 2024. All rights reserved.