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

时间:2014-09-01 09:57:07

标签: c++ c++11 lambda template-meta-programming typetraits

编辑: 由于一些原因,问题中概述的方法存在问题。最后,我通过不同的方式解决了这个问题,请参阅下面的答案。

我有一些模板类,其中模板参数应该是与某个签名匹配的可调用对象。如果用户提供的模板参数不可调用或与预期的签名不匹配,则编译在回调机制内部深度失败,并且生成的错误消息很难解密。相反,如果给定的模板参数无效,我希望能够使用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的匿名仿函数,所以我无法理解为什么前者有效但后者没有。

总而言之,我的问题是:

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

在此先感谢,这正在推动我的模板元编程知识的极限,因此任何建议都会感激不尽。

2 个答案:

答案 0 :(得分:2)

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

根据回复,特别是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)更加优雅。对于我试图抛出的每个可调用对象,它也能正常工作。

尽管如此,正如我在原始问题中所说,这推动了我的元编程知识的极限,所以如果对如何改进此代码有任何建议,请告诉我。

答案 1 :(得分:1)

您错过const中的operator ()说明符。

使用:

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; };