检查可调用模板参数类型

Tri*_*dle 5 c++ lambda type-traits template-meta-programming c++11

编辑: 问题中概述的方法由于一些原因而存在问题。最后,我以另一种方式解决了这个问题,请参见下面的答案。

我有一些模板类,其中template参数应该是与某个签名匹配的可调用对象。如果用户提供了一个不可调用的模板参数或与预期的签名不匹配的模板参数,则编译将在回调机制内部深处失败,并且所产生的错误消息将很难破译。相反,static_assert如果给定的template参数无效,我想能够用来预先提供一个很好的,易于理解的错误消息。不幸的是,这似乎很难做到。

我正在使用以下设置:

#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;
};
Run Code Online (Sandbox Code Playgroud)

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

这似乎适用于简单的函数和用户定义的函子,但是由于某些原因,我无法理解它对于lambda失败(已通过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");
}
Run Code Online (Sandbox Code Playgroud)

我的理解是该标准要求将lambda实现为与g上述等效的匿名函子,因此我无法理解为什么前者起作用,而后者却不起作用。

因此,总而言之,我的问题是:

  • 我在这里使用的方法实际上合理吗,还是我犯了一个明显的错误?
  • 为什么这对于用户定义的函子似乎有效,但对于编译器定义的函子(即lambda)却无效?
  • 是否有修复程序/变通办法,以便可以这种方式检查lambda?
  • 我可以对该代码进行其他改进吗?(可能很多...)

在此先感谢您,这使我的模板元编程知识受到了限制,因此将不胜感激地接受任何建议。

Tri*_*dle 5

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

根据回复,特别是 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 {};
Run Code Online (Sandbox Code Playgroud)

declval这将使用可调用本身和参数构造一个“虚拟”函数调用,并检查结果是否可以转换为所需的类型。如果这样的调用无效,SFINAE 就会启动并拒绝部分特化。

这比我之前尝试做的更短并且(IMO)更优雅。它也适用于我尝试扔给它的每个可调用对象。

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