2014-09-01 68 views
4

编辑:由于几个原因,问题中概述的方法存在问题。最后,我以不同的方式解决了这个问题,请参阅下面的答案。检查可召回的模板参数类型

我有一些模板类,其中的模板参数预计是一个可调用匹配某个签名。如果用户提供的模板参数不是可调用的或者与预期的签名不匹配,那么编译将在回调机制内部深陷失败,并且产生的错误消息很难破译。相反,如果给定的模板参数无效,我希望能够使用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()是相反。

这似乎是一些简单的功能和用户定义的函数子工作,但由于某种原因,我无法理解失败的lambda表达式(铿锵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"); 
} 

我的理解是该标准要求lambda类实现为等效于上面g的匿名函子,所以我不明白为什么前者可行,但后者不行。

因此,总的来说,我的问题:

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

在此先感谢,这是推动我的模板元编程知识的限制,所以任何建议将感激地收到。

+1

来自[此答案](http://stackoverflow.com/a/12283159/3920237)代码似乎工作。 [现场示例](http://coliru.stacked-crooked.com/a/f951bb2b4ca90efd) – 2014-09-01 11:02:41

回答

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

您错过了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; 
}; 

的检查是正确的(非可变)拉姆达(但不包括您的自定义可变仿函数)。 否则你必须让你的λ可变:

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