正在检查可调用的模板参数类型

Checking callable template parameter types

本文关键字:参数 类型 调用 检查      更新时间:2023-10-16

编辑: 问题中概述的方法存在问题,原因如下。最后,我用不同的方式解决了这个问题,见下面的答案

我有一些模板类,其中的模板参数应该是与某个签名匹配的可调用参数。如果用户提供的模板参数要么不可调用,要么与预期签名不匹配,那么编译将在回调机制内部失败,由此产生的错误消息很难解密。相反,如果给定的模板参数无效,我希望能够使用static_assert提前提供一个好的、易于理解的错误消息。不幸的是,这似乎很难做到

我使用以下设置:

#include <type_traits>
namespace detail {
template <typename Function, typename Sig>
struct check_function
{
    static constexpr bool value =
        std::is_convertible<Function, typename std::decay<Sig>::type>::value;
};
template <typename, typename>
struct check_functor;
template <typename Functor, typename Ret, typename... Args>
struct check_functor<Functor, Ret(Args...)>
{
    typedef Ret (Functor::*Memfn) (Args...);
    static constexpr bool value =
        check_function<decltype(&Functor::operator()), Memfn>::value;
};
} // end namespace detail
template <typename Func, typename Sig>
struct check_function_signature
{
    using Type =
        typename std::conditional<
                     std::is_function<Func>::value,
                     detail::check_function<Func, Sig>,
                     detail::check_functor<Func, Sig>>::type;
    static constexpr bool value = Type::value;
};

即,如果Func是函数指针类型,则将其直接与所需签名进行比较,否则将其假定为函子,并将其operator()进行比较。

这似乎适用于简单函数和用户定义的函数,但出于某种原因,我无法理解它在lambdas(用Clang 3.4和g++4.8测试)中失败:

int f(int i);
struct g
{
    int operator() (int i) { return i; }
};
int main()
{
    static_assert(check_function_signature<decltype(f), int(int)>::value,
                  "Fine");
    static_assert(check_function_signature<g, int(int)>::value,
                  "Also fine");
    auto l = [](int i) { return i; };
    static_assert(check_function_signature<decltype(l), int(int)>::value,
                  "Fails");
}

我的理解是,标准要求lambdas被实现为与上面的g等价的匿名函子,所以我不明白为什么前者有效,而后者无效。

总之,我的问题是:

  • 我在这里使用的方法真的合理吗,还是我犯了一个明显的错误
  • 为什么这似乎适用于用户定义的函子,但对于编译器定义的函函数(即lambdas)却失败了
  • 是否有一个修复/解决方法可以用这种方式检查Lambda
  • 我对这个代码还有其他改进吗?(可能很多…)

提前感谢,这已经突破了我的模板元编程知识的极限,所以任何建议都将不胜感激。

(回答我自己的问题,因为我找到了更好的方法来实现我想要的,并认为我会分享它。)

根据这些响应,特别是remaybel评论中的链接答案,我最终得到了大量的代码,这些代码从函子的operator()中剥离了类类型,并根据所需的签名检查了每个参数类型。然而,这并不能很好地工作,因为获得指向T::operator()的成员指针的要求意味着,如果operator()有多个重载,或者它被定义为模板,它就会失败。我也不确定它在所有情况下都能正确处理参数转换,而且有很多事情很难纠正。

经过进一步思考,我意识到我真正想做的是尝试用我所需的参数类型构造一个函数调用,如果不能进行这样的调用,就会失败。后来进行了一点黑客攻击,我想出了这个:

template <typename, typename, typename = void>
struct check_signature : std::false_type {};
template <typename Func, typename Ret, typename... Args>
struct check_signature<Func, Ret(Args...),
    typename std::enable_if<
        std::is_convertible<
            decltype(std::declval<Func>()(std::declval<Args>()...)),
            Ret
        >::value, void>::type>
    : std::true_type {};

这将使用declval为可调用对象本身和参数构造一个"伪"函数调用,并检查结果是否可以转换为所需的类型。如果这样的呼吁无效,SFINAE将介入,部分专业化将被拒绝。

这比我以前想做的要短得多,(IMO)也优雅得多。它对我试图抛出的每一个可调用项都能正常工作

尽管如此,正如我在最初的问题中所说,这已经突破了我元编程知识的极限,所以如果有任何关于如何改进这段代码的建议,请告诉我。

您在operator ()中缺少const说明符。

带有:

template <typename Functor, typename Ret, typename... Args>
struct check_functor<Functor, Ret(Args...)>
{
    typedef Ret (Functor::*Memfn) (Args...) const; // const added here
    static constexpr bool value =
        check_function<decltype(&Functor::operator()), Memfn>::value;
};

检查对于(不可变的)lambda是正确的(但对于您的自定义可变函子不是正确的)。否则,您必须使lambda可变:

auto l = [](int i) mutable { return i; };