代码之家  ›  专栏  ›  技术社区  ›  111111

确定作为模板参数传递的函数的参数类型

  •  1
  • 111111  · 技术社区  · 6 月前

    给定一个作为模板参数传递给函数的函数,我如何确定它的第一个参数的类型?

    例如:

    template<auto Func>
    void run_func(/* type of argument???? */ value) {
        Func(value);
    }
    

    这个特殊的用例要求Func是一个编译时常数。如果可能的话,我更喜欢从 Func 而不是添加另一个模板参数,因为我希望能够像这样引用函数:

    auto function_runner = &run_func<&func>;
    

    谢谢

    2 回复  |  直到 6 月前
        1
  •  2
  •   Jan Schultke    6 月前

    函数的类型在编译时是已知的,可以使用 std::remove_pointer_t<decltype(Func)> . 您必须编写一个类型特征,为您提供有关参数的信息,例如 parameters<std::remove_pointer_t<decltype(Func)>>::type ;参见 Get types of C++ function parameters

    这看起来像:

    template<typename Sig>
    struct parameters;
    
    // extremely imperfect solution because it doesn't work for noexcept,
    // const, volatile, reference-qualified, or variadic functions
    template<typename R, typename ...Args>
    struct parameters<R(Args...)>
    {
        using type = std::tuple<Args...>;
    };
    
    // example is std::tuple<int, float>;
    using example = parameters<void(int, float)>::type;
    

    不过,制定一个适当的解决方案并非易事,因为你需要编写48个部分专门化来涵盖每种函数,而不仅仅是那些没有cv或引用限定符的函数 noexcept 如示例所示。

    在大多数情况下,您不需要知道参数的类型(或第一个参数)。也许你可以简单地问

    我可以用以下命令调用此函数吗 int ?

    而不是:

    第一个参数的类型是什么?确保它是 int .

    前一个问题可以很容易地回答 std::is_invocable .

    然后,您可以使用C++26包索引, std::tuple_element ,或通过其他方式获取元组中的第一个类型。

    最终的解决方案看起来像:

    // based on std::invocable test
    template<auto Func, typename T>
      requires std::invocable<decltype(Func), T&&>
    void run_func(T&& value) {
        Func(std::forward<T>(value));
    }
    
    // based on extracting the first parameter type
    template <typename T>
    using function_pointer_first_parameter_t
        = std::tuple_element_t<0, std::remove_pointer_t<Func>>;
    
    template<auto Func>
    void run_func(function_pointer_first_parameter_t<decltype(Func)> value) {
        Func(std::forward<decltype(value)>(value));
    } 
    

    后一种解决方案的一个明显问题是,您不必要地创建了中间对象。 例如,如果参数类型为 std::string ,您不必要地调用move构造函数将其传递给 Func ,因为 value 是一个单独的对象。呼叫者也可能有自己的 std::string 对象之上,所以总共有三个单独的对象。

    此外,如果你有一个参数,比如字符串文字(类型 const char[N] )如果 函数 std::string 按价值计算,那么你也错过了简单地调用 std::string(const char*) 构造函数,而不是创建不必要的临时 std::string 对象。

    可以说,这就是为什么后一种解决方案严格来说比前一种方案差的原因,尽管它少了一个模板参数,乍一看可能看起来更优雅。

        2
  •  0
  •   Holt 111111    6 月前

    好吧,我找到了一个解决方案,也许有人有更好的解决方案:

    template<typename T> struct decode { };
    
    template<typename T> struct decode< void(*)(T) > { 
        using type = T;
    };
    
    template<auto Func>
    void run_func(typename decode<decltype(Func)>::type value) {
        Func(value);
    }