From 079030818dcfadd77efd9cef5f688a06947adcee Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sat, 20 Jun 2009 04:43:52 +0200 Subject: [PATCH] draft a test to sharpen the idea of the function holder (erasure) --- src/lib/meta/function-closure.hpp | 162 +------------------- src/lib/meta/function-erasure.hpp | 80 ++++++---- tests/40components.tests | 10 ++ tests/lib/meta/dummy-functions.hpp | 60 ++++++++ tests/lib/meta/function-closure-test.cpp | 31 +--- tests/lib/meta/function-erasure-test.cpp | 185 +++++++++++++++++++---- 6 files changed, 283 insertions(+), 245 deletions(-) create mode 100644 tests/lib/meta/dummy-functions.hpp diff --git a/src/lib/meta/function-closure.hpp b/src/lib/meta/function-closure.hpp index ce81a89dd..85a9c7ef7 100644 --- a/src/lib/meta/function-closure.hpp +++ b/src/lib/meta/function-closure.hpp @@ -37,28 +37,22 @@ #ifndef LUMIERA_META_FUNCTION_CLOSURE_H #define LUMIERA_META_FUNCTION_CLOSURE_H -#include "lib/meta/typelist.hpp" /////////////TODO -#include "lib/meta/generator.hpp" /////////////TODO #include "lib/meta/function.hpp" #include "lib/meta/tuple.hpp" #include -#include "lib/util.hpp" ////////////////////////TODO -#include "lib/error.hpp" namespace lumiera { namespace typelist{ - using std::tr1::bind; - //using std::tr1::placeholders::_1; - //using std::tr1::placeholders::_2; using std::tr1::function; + using std::tr1::bind; - namespace func { // helpers for binding and applying a function to an argument tuple + namespace func { ///< helpers for binding and applying a function to an argument tuple using tuple::element; @@ -392,157 +386,5 @@ namespace typelist{ - /** - * Generic wrapper carrying a function object - * while hiding the actual function signature - * @note not statically typesafe. Depending on - * the actual embedded container type, - * it \em might be run-time typesafe. - */ - template - struct FunErasure - : FH - { - template - FunErasure (FUN const& functor) - : FH(functor) - { } - }; - - /** - * Policy for FunErasure: store an embedded tr1::function - * Using this policy allows to store arbitrary complex functor objects - * embedded within a neutral container and retrieving them later type-safe. - * The price to pay is vtable access and heap storage of function arguments. - */ - class StoreFunction - { - /** Helper: type erasure */ - struct Holder - { - enum { SIZE = sizeof(function) }; - char storage_[SIZE]; - virtual ~Holder() {} - }; - - /** embedding the concrete functor object */ - template - struct FunctionHolder : Holder - { - typedef function Functor; - FunctionHolder (SIG& fun) - { - REQUIRE (SIZE >= sizeof(Functor)); - new(&storage_) Functor (fun); - } - ~FunctionHolder() - { - get()->~Functor(); - } - Functor& - get() - { - return static_cast (&storage_); - } - }; - - /** embedded buffer actually holding - * the concrete Functor object */ - Holder holder_; - - public: - template - StoreFunction (SIG& fun) - { - new(&holder_) FunctionHolder (fun); - } - - template - function& - getFun () - { - REQUIRE (INSTANCEOF (FunctionHolder, &holder_)); - return static_cast&> (holder_).get(); - } - }; - - - /** - * Policy for FunErasure: store a bare function pointer. - * Using this policy allows to store a conventional function ptr, - * while still being able to re-access it later with run-time typecheck. - * The price to pay is vtable access. - */ - class StoreFunPtr - { - /** Helper: type erasure */ - struct Holder - { - void *fun_; - virtual ~Holder() {} - }; - - /** storing and retrieving concrete function ptr */ - template - struct FunctionHolder : Holder - { - FunctionHolder (SIG& fun) - { - fun_ = &fun; - } - SIG& - get() - { - return reinterpret_cast (&fun_); - } - }; - - /** embedded container holding the pointer */ - Holder holder_; - - public: - template - StoreFunPtr (SIG& fun) - { - new(&holder_) FunctionHolder (fun); - } - - template - function& - getFun () - { - REQUIRE (INSTANCEOF (FunctionHolder, &holder_)); - return static_cast&> (holder_).get(); - } - }; - - - /** - * Policy for FunErasure: store an unchecked bare function pointer. - * Using this policy allows to store a conventional function ptr, - * and to retrieve it without overhead, but also without safety. - */ - class StoreUncheckedFunPtr - { - void *fun_; - - public: - template - StoreUncheckedFunPtr (SIG& fun) - { - fun_ = &fun; - } - - template - SIG& - getFun () - { - return reinterpret_cast (&fun_); - } - }; - - - - }} // namespace lumiera::typelist #endif diff --git a/src/lib/meta/function-erasure.hpp b/src/lib/meta/function-erasure.hpp index 5f29c783b..7e18ba090 100644 --- a/src/lib/meta/function-erasure.hpp +++ b/src/lib/meta/function-erasure.hpp @@ -22,14 +22,30 @@ /** @file function-erasure.hpp - ** Partial function application and building a complete function closure. - ** This is a small addendum to (and thin wrapper for) tr1/functional, supporting - ** the case when a function should be closed over (all) arguments, where especially - ** the parameter values to close on are provided as a tuple. + ** When working with generic function objects and function pointers typed to + ** arbitrary signatures, often there is the necessity to hold onto such a functor + ** while hiding the actual signature behind an common interface ("type erasure"). + ** The usual solution based on subclassing has the downside of requiring separate + ** storage for the concrete functor object, which might become problematic when + ** dealing with lots of functor objects. ** - ** @see control::CommandDef usage example - ** @see function.hpp - ** @see tuple.hpp + ** Especially when dealing with tr1::function objects, all of the type differences + ** are actually encoded into 3 internal pointers, thus yielding the same size for + ** all various types of functors. Building on this observation, we can create an + ** common container object to store the varying functors inline, while hiding the + ** actual signature. + ** + ** There remains the problem of re-accessing the concrete functor later on. As + ** C++ has only rudimental introspection capabilities, we can only rely on the + ** usage context to provide the correct function signature; only when using a + ** virtual function for the re-access, we can perform at least a runtime-check. + ** + ** Thus there are various flavours for actually implementing this idea, and the + ** picking a suitable implementation depends largely on the context. Thus we + ** provide a common and expect the client code to pick an implementation policy. + ** + ** @see control::Mutation usage example + ** @see function-erasure-test.cpp ** */ @@ -37,23 +53,15 @@ #ifndef LUMIERA_META_FUNCTION_ERASURE_H #define LUMIERA_META_FUNCTION_ERASURE_H -#include "lib/meta/typelist.hpp" /////////////TODO -#include "lib/meta/generator.hpp" /////////////TODO -#include "lib/meta/function.hpp" -#include "lib/meta/tuple.hpp" +#include "lib/util.hpp" +#include "lib/error.hpp" #include -#include "lib/util.hpp" ////////////////////////TODO -#include "lib/error.hpp" - namespace lumiera { namespace typelist{ - using std::tr1::bind; - //using std::tr1::placeholders::_1; - //using std::tr1::placeholders::_2; using std::tr1::function; @@ -96,19 +104,19 @@ namespace typelist{ struct FunctionHolder : Holder { typedef function Functor; - FunctionHolder (SIG& fun) + FunctionHolder (Functor const& fun) { REQUIRE (SIZE >= sizeof(Functor)); new(&storage_) Functor (fun); } ~FunctionHolder() { - get()->~Functor(); + get().~Functor(); } Functor& get() { - return static_cast (&storage_); + return *reinterpret_cast (&storage_); } }; @@ -119,6 +127,11 @@ namespace typelist{ public: template StoreFunction (SIG& fun) + { + new(&holder_) FunctionHolder (function(fun)); + } + template + StoreFunction (function const& fun) { new(&holder_) FunctionHolder (fun); } @@ -144,7 +157,7 @@ namespace typelist{ /** Helper: type erasure */ struct Holder { - void *fun_; + void *fP_; virtual ~Holder() {} }; @@ -152,14 +165,15 @@ namespace typelist{ template struct FunctionHolder : Holder { - FunctionHolder (SIG& fun) + FunctionHolder (SIG *fun) { - fun_ = &fun; + REQUIRE (fun); + fP_ = reinterpret_cast (fun); } SIG& get() { - return reinterpret_cast (&fun_); + return *reinterpret_cast (&fP_); } }; @@ -169,12 +183,17 @@ namespace typelist{ public: template StoreFunPtr (SIG& fun) + { + new(&holder_) FunctionHolder (&fun); + } + template + StoreFunPtr (SIG *fun) { new(&holder_) FunctionHolder (fun); } template - function& + SIG& getFun () { REQUIRE (INSTANCEOF (FunctionHolder, &holder_)); @@ -190,20 +209,25 @@ namespace typelist{ */ class StoreUncheckedFunPtr { - void *fun_; + void *funP_; public: template StoreUncheckedFunPtr (SIG& fun) { - fun_ = &fun; + funP_ = reinterpret_cast (&fun); + } + template + StoreUncheckedFunPtr (SIG *fun) + { + funP_ = reinterpret_cast (fun); } template SIG& getFun () { - return reinterpret_cast (&fun_); + return *reinterpret_cast (&funP_); } }; diff --git a/tests/40components.tests b/tests/40components.tests index 7a87ab1ad..e261ba5d0 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -213,6 +213,16 @@ out: dtor ~TargetObj\(12\) successful END +PLANNED "FunctionClosure_test" FunctionClosure_test < + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#ifndef LIB_META_DUMMY_FUNCTIONS_H +#define LIB_META_DUMMY_FUNCTIONS_H + + +//#include +//#include + +//using std::string; +//using boost::format; + + +namespace lumiera { +namespace typelist{ +namespace test { + + + namespace { // test helpers + + int _sum_ =0; ///< used to verify the effect of testFunc + + void + testFunc (int i, char c) + { + _sum_ += i + c; + } + + int + returnIt () + { + return _sum_; + } + } + + + +}}} // namespace lumiera::typelist::test +#endif diff --git a/tests/lib/meta/function-closure-test.cpp b/tests/lib/meta/function-closure-test.cpp index 78b205925..6218061a6 100644 --- a/tests/lib/meta/function-closure-test.cpp +++ b/tests/lib/meta/function-closure-test.cpp @@ -22,7 +22,8 @@ /** @file function-closure-test.cpp - ** bla + ** + ** @todo define function-closure-test ** ** @see function-closure.hpp ** @see control::CmdClosure real world usage example @@ -32,7 +33,8 @@ #include "lib/test/run.hpp" #include "lib/meta/function-closure.hpp" -#include "lib/util.hpp" +#include "meta/dummy-functions.hpp" +//#include "lib/util.hpp" //#include #include @@ -47,13 +49,6 @@ namespace typelist{ namespace test { - namespace { // test data - - } // (End) test data - - - - /************************************************************************* @@ -62,23 +57,11 @@ namespace test { */ class FunctionClosure_test : public Test { - virtual void run(Arg arg) + virtual void + run (Arg) { - check_distribute(); - check_combine(); + UNIMPLEMENTED ("verify function closure utils"); } - - - void check_distribute() - { - } - - - void check_combine() - { - } - - }; diff --git a/tests/lib/meta/function-erasure-test.cpp b/tests/lib/meta/function-erasure-test.cpp index c615edbc7..46c611f52 100644 --- a/tests/lib/meta/function-erasure-test.cpp +++ b/tests/lib/meta/function-erasure-test.cpp @@ -21,20 +21,13 @@ * *****************************************************/ -/** @file function-erasure-test.cpp - ** bla - ** - ** @see control::FunErasure - ** @see command-mutation.hpp real world usage example - ** - */ - - #include "lib/test/run.hpp" #include "lib/meta/function-erasure.hpp" -#include "lib/util.hpp" +#include "meta/dummy-functions.hpp" +//#include "lib/util.hpp" //#include +#include #include using ::test::Test; @@ -46,38 +39,164 @@ namespace lumiera { namespace typelist{ namespace test { + using std::tr1::function; + using std::tr1::bind; + - namespace { // test data - - } // (End) test data - - - - - + typedef FunErasure Efun; + typedef FunErasure Efp; + typedef FunErasure Evoid; /************************************************************************* - * @test ////////////////////////////////////////// - * - building combinations and permutations + * @test Create specifically typed functor objects and then wrap them + * into common holder objects, thereby discarding the specific + * signature type information. Later on, the concrete functor + * can be re-accessed, given the exact and specific type. + * + * @see control::FunErasure + * @see command-mutation.hpp real world usage example */ class FunctionErasure_test : public Test { - virtual void run(Arg arg) - { - check_distribute(); - check_combine(); - } - - - void check_distribute() - { - } - - - void check_combine() + virtual void + run (Arg) { + function bindFunc = bind (testFunc,_1,_2); + function pAplFunc = bind (testFunc,_1,'x'); + function memFunc = bind (&FunctionErasure_test::testMemberFunction,this, _1); + + function getterFunc = &returnIt; + + check_FunctorContainer( Efun (testFunc) + , Efun (bindFunc) + , Efun (pAplFunc) + , Efun (memFunc) + , Efun (getterFunc) + ); + + check_FunctPtrHolder(Efp(testFunc) + ,Efp(&testFunc) + , Efp(returnIt)); + + check_VoidPtrHolder(Evoid(testFunc) + ,Evoid(&testFunc) + , Evoid(returnIt)); } + void + testMemberFunction (char c) + { + return testFunc('a'-'A', c); + } + + + void + check_FunctorContainer (Efun f1, Efun f2, Efun f3, Efun f4, Efun f5) + { + typedef void (Sig1) (int,char); + typedef void (Sig2) (int); + typedef void (Sig3) (char); + typedef int (Sig4) (); + + _sum_ = 0; + f1.getFun() (-11,'M'); + ASSERT (_sum_ == 'M'-11); + + _sum_ = 0; + f2.getFun() (-22,'M'); + ASSERT (_sum_ == 'M'-22); + + _sum_ = 0; + f3.getFun() (-33); + ASSERT (_sum_ == 'x'-22); + + _sum_ = 0; + f4.getFun() ('U'); + ASSERT (_sum_ == 'u'); + + ASSERT ( 'u' == f5.getFun() () ); + +#if false ////////////////////////////////////////////////////////TODO: restore throwing ASSERT + VERIFY_ERROR (ASSERTION, f1.getFun() ); + VERIFY_ERROR (ASSERTION, f1.getFun() ); + VERIFY_ERROR (ASSERTION, f1.getFun() ); + + VERIFY_ERROR (ASSERTION, f2.getFun() ); + VERIFY_ERROR (ASSERTION, f3.getFun() ); + VERIFY_ERROR (ASSERTION, f2.getFun() ); + + VERIFY_ERROR (ASSERTION, f3.getFun() ); + VERIFY_ERROR (ASSERTION, f3.getFun() ); + VERIFY_ERROR (ASSERTION, f3.getFun() ); + + VERIFY_ERROR (ASSERTION, f4.getFun() ); + VERIFY_ERROR (ASSERTION, f4.getFun() ); + VERIFY_ERROR (ASSERTION, f4.getFun() ); + + VERIFY_ERROR (ASSERTION, f5.getFun() ); + VERIFY_ERROR (ASSERTION, f5.getFun() ); + VERIFY_ERROR (ASSERTION, f5.getFun() ); +#endif + } + + + void + check_FunctPtrHolder (Efp f1, Efp f2, Efp f3) + { + typedef void (*FP)(int,char); + typedef void (&FR)(int,char); + + FP fun1 = &f1.getFun(); + FP fun2 = &f2.getFun(); + FR fun2r = f2.getFun(); + + _sum_ = 0; + (*fun1) (10, 'a'); + ASSERT (_sum_ == 10+'a'); + + (*fun2) (20, 'b'); + ASSERT (_sum_ == 10+'a'+20+'b'); + + fun2r (30, 'c'); + ASSERT (_sum_ == 10+'a'+20+'b'+30+'c'); + + ASSERT (_sum_ == (f3.getFun()) () ); + +#if false ////////////////////////////////////////////////////////TODO: restore throwing ASSERT + VERIFY_ERROR (ASSERTION, f1.getFun() ); +#endif + + } + + + void + check_VoidPtrHolder (Evoid f1, Evoid f2, Evoid f3) + { + typedef void (*FP)(int,char); + typedef void (&FR)(int,char); + + FP fun1 = &f1.getFun(); + FP fun2 = &f2.getFun(); + FR fun2r = f2.getFun(); + + _sum_ = 0; + (*fun1) (10, 'a'); + ASSERT (_sum_ == 10+'a'); + + (*fun2) (20, 'b'); + ASSERT (_sum_ == 10+'a'+20+'b'); + + fun2r (30, 'c'); + ASSERT (_sum_ == 10+'a'+20+'b'+30+'c'); + + ASSERT (_sum_ == (f3.getFun()) () ); + + FP bad_fun = &f3.getFun(); + ASSERT ((void*)bad_fun == &returnIt); + + (*bad_fun) (11, 'x'); // SEGV + } + };