Library: improve the function-signature detector to work as guard with enable_if

This is a consequence of the experiments with generic lambdas.
Up to now, lib::meta::_Fun<F> failed with a compilation error
when passing the decltype of such a generic lambda.

The new behaviour is to pick the empty specialisation (std::false_type) in such cases,
allowing to guard explicit specialisations when no suitable functor type
is passed
This commit is contained in:
Fischlurch 2017-11-24 02:48:16 +01:00
parent 8bdd9e7d66
commit 3614085ff7
4 changed files with 77 additions and 88 deletions

View file

@ -78,79 +78,8 @@ namespace meta{
using Sig = Ret(ARGS...);
};
template<typename FUN>
class can_Invoke
{
template<typename FF,
typename SEL = decltype(&FF::operator())>
struct Probe
{ };
template<class X>
static Yes_t check(Probe<X> * );
template<class>
static No_t check(...);
public:
static const bool value = (sizeof(Yes_t)==sizeof(check<FUN>(0)));
};
template<typename FUN, typename SEL =void>
struct _FunT
: std::false_type
{ };
template<typename FUN>
struct _FunT<FUN, enable_if<can_Invoke<FUN>> >
: _FunT<decltype(&FUN::operator())>
{ };
/** Specialisation for a bare function signature */
template<typename RET, typename...ARGS>
struct _FunT<RET(ARGS...)>
: std::true_type
{
using Ret = RET;
using Args = Types<ARGS...>;
using Sig = RET(ARGS...);
};
/** Specialisation for using a function pointer */
template<typename SIG>
struct _FunT<SIG*>
: _FunT<SIG>
{ };
/** Specialisation when using a function reference */
template<typename SIG>
struct _FunT<SIG&>
: _FunT<SIG>
{ };
/** Specialisation for passing a rvalue reference */
template<typename SIG>
struct _FunT<SIG&&>
: _FunT<SIG>
{ };
/** Specialisation to deal with member pointer to function */
template<class C, typename RET, typename...ARGS>
struct _FunT<RET (C::*) (ARGS...)>
: _FunT<RET(ARGS...)>
{ };
/** Specialisation to handle member pointer to const function;
* indirectly this specialisation also handles lambdas,
* as redirected by the main template (via `decltype`) */
template<class C, typename RET, typename...ARGS>
struct _FunT<RET (C::*) (ARGS...) const>
: _FunT<RET(ARGS...)>
{ };
}}
using lib::meta::Types;
using lib::meta::_FunT;
using lib::meta::enable_if;
@ -176,9 +105,9 @@ struct FunTrait
};
template<typename FUN>
struct FunTrait<FUN, enable_if<_FunT<FUN>> >
struct FunTrait<FUN, enable_if<_Fun<FUN>> >
{
static string doIt() { return "Yeah FUN:" + lib::meta::typeStr<typename _FunT<FUN>::Sig>(); }
static string doIt() { return "Yeah FUN:" + lib::meta::typeStr<typename _Fun<FUN>::Sig>(); }
};
int
@ -190,13 +119,13 @@ main (int, char**)
SHOW_TYPE (decltype(lamb1));
SHOW_TYPE (decltype(lamb2));
SHOW_EXPR ((_FunT<decltype(lamb1)>::value));
SHOW_EXPR ((_FunT<decltype(lamb2)>::value));
SHOW_EXPR ((_FunT<decltype(funny)>::value));
SHOW_EXPR ((_FunT<decltype(&funny)>::value));
SHOW_EXPR ((_Fun<decltype(lamb1)>::value));
SHOW_EXPR ((_Fun<decltype(lamb2)>::value));
SHOW_EXPR ((_Fun<decltype(funny)>::value));
SHOW_EXPR ((_Fun<decltype(&funny)>::value));
auto funky = function<double(float)> (lamb2);
SHOW_EXPR ((_FunT<decltype(funky)>::value));
SHOW_EXPR ((_Fun<decltype(funky)>::value));
cout << FunTrait<decltype(lamb1)>::doIt() <<endl;
cout << FunTrait<decltype(lamb2)>::doIt() <<endl;

View file

@ -52,6 +52,7 @@
#define LIB_META_FUNCTION_H
#include "lib/meta/typelist.hpp"
#include "lib/meta/util.hpp"
#include <functional>
@ -73,7 +74,11 @@ namespace meta{
* as function reference, function pointer, member function pointer,
* functor object, `std::function` or lambda.
*
* The base case assumes a functor object, i.e. anything with an `operator()`.
* This template can also be used in metaprogramming with `enable_if` to enable
* some definition or specialisation only if a function-like type was detected; thus
* the base case holds no nested type definitions and inherits from std::false_type.
* The primary, catch-all case gets activated whenever on functor objects, i.e. anything
* with an `operator()`.
* The following explicit specialisations handle the other cases, which are
* not objects, but primitive types (function (member) pointers and references).
* @remarks The key trick of this solution is to rely on `decltype` of `operator()`
@ -82,19 +87,35 @@ namespace meta{
* function signature are reflected. But if you bind such a member
* pointer into a `std::function`, an additional first parameter
* will show up to take the `this` pointer of the class instance.
* @warning this detection scheme fails when the signature of a function call
* operator is ambiguous, which is especially the case
* - when there are several overloads of `operator()`
* - when the function call operator is templated
* - on *generic lambdas*
* All these cases will activate the base (false) case, as if the
* tested subject was not a function at all. Generally speaking,
* it is _not possible_ to probe a generic lambda or templated function,
* unless you bind it beforehand into a std::function with correct signature.
* @see FunctionSignature_test
*
* [kennytm]: http://stackoverflow.com/users/224671/kennytm
* [stackoverflow] : http://stackoverflow.com/questions/7943525/is-it-possible-to-figure-out-the-parameter-type-and-return-type-of-a-lambda/7943765#7943765 "answer on stackoverflow"
*/
template<typename FUN>
template<typename FUN, typename SEL =void>
struct _Fun
: std::false_type
{ };
/** Specialisation for function objects and lambdas */
template<typename FUN>
struct _Fun<FUN, enable_if<has_FunctionOperator<FUN>> >
: _Fun<decltype(&FUN::operator())>
{ };
/** Specialisation for a bare function signature */
template<typename RET, typename...ARGS>
struct _Fun<RET(ARGS...)>
: std::true_type
{
using Ret = RET;
using Args = Types<ARGS...>;
@ -134,12 +155,6 @@ namespace meta{
template<typename FUN>
struct is_Functor { static const bool value = false; };
template<typename SIG>
struct is_Functor<function<SIG> > { static const bool value = true; };

View file

@ -25,7 +25,7 @@
** Simple and lightweight helpers for metaprogramming and type detection.
** This header is a collection of very basic type detection and metaprogramming utilities.
** @warning indirectly, this header gets included into the majority of compilation units.
** Avoid anything here which increases compilation times or adds much debugging info.
** Avoid anything here which increases compilation times or adds much debugging info.
**
** @see MetaUtils_test
** @see trait.hpp
@ -173,11 +173,34 @@ namespace meta {
template<class>
static No_t check(...);
public:
public:
static const bool value = (sizeof(Yes_t)==sizeof(check<TY>(0)));
};
/** Trait template to detect presence of a simple function call operator
* @note this metafunction fails to detect an overloaded or templated `operator()`
*/
template<typename FUN>
class has_FunctionOperator
{
template<typename FF,
typename SEL = decltype(&FF::operator())>
struct Probe
{ };
template<class X>
static Yes_t check(Probe<X> * );
template<class>
static No_t check(...);
public:
static const bool value = (sizeof(Yes_t)==sizeof(check<FUN>(0)));
};

View file

@ -213,6 +213,28 @@ namespace test {
CHECK ("int (unsigned int)" == typeStr<_Fun<decltype(memfunP)>::Sig>());
CHECK ("int (Functor&, unsigned int)" == typeStr<_Fun<decltype(fM)>::Sig >());
CHECK ("int (unsigned int)" == typeStr<_Fun<decltype(fMF)>::Sig >());
// _Fun<F> can be used for metaprogramming with enable_if
CHECK ( _Fun<Func>::value); // yes : a Functor
CHECK ( _Fun<int(long)>::value); // yes : a function type
CHECK (not _Fun<int>::value); // no : a type without function call operator
auto lambda1 = [](int i) { return double(i) / (i*i); };
auto lambda2 = [](auto i) { return double(i) / (i*i); };
using TLamb1 = decltype(lambda1);
using TLamb2 = decltype(lambda2);
CHECK ( _Fun<TLamb1>::value); // yes : detect signature of lambda
CHECK (not _Fun<TLamb2>::value); // no : can not detect signature of a generic lambda!
// but detection works, once the templated operator() has been instantiated to some fixed type
auto stdFunction = function<double(float)> (lambda2);
CHECK ( _Fun<decltype(stdFunction)>::value);
CHECK ("double (int)" == showSig (lambda1));
CHECK ("double (float)" == showSig (stdFunction));
}
};