diff --git a/BaseLib/Functional.cpp b/BaseLib/Functional.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e2f71555c365425c0801a7d0bfd7fa2db6e3d5a7 --- /dev/null +++ b/BaseLib/Functional.cpp @@ -0,0 +1,33 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include "Functional.h" + +namespace BaseLib +{ +namespace detail +{ +#define DEFINE_INDEXEDPLACEHOLDER_MEMBER(INDEX, INDEX_P_1) \ + const decltype(std::placeholders::_##INDEX_P_1) \ + IndexedPlacedPlaceholder<(INDEX)>::value = \ + std::placeholders::_##INDEX_P_1 + +DEFINE_INDEXEDPLACEHOLDER_MEMBER(0, 1); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(1, 2); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(2, 3); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(3, 4); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(4, 5); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(5, 6); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(6, 7); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(7, 8); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(8, 9); +DEFINE_INDEXEDPLACEHOLDER_MEMBER(9, 10); +} + +} // namespace BaseLib diff --git a/BaseLib/Functional.h b/BaseLib/Functional.h new file mode 100644 index 0000000000000000000000000000000000000000..841ae244d3caea49ce6a39e26192b01b7d421cc9 --- /dev/null +++ b/BaseLib/Functional.h @@ -0,0 +1,216 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef BASELIB_FUNCTIONAL_H +#define BASELIB_FUNCTIONAL_H + +#include <functional> +#include "BaseLib/TMPUtil.h" + +namespace BaseLib +{ +namespace detail +{ +//! Helper struct used to make std::placeholders::_1, ... accessible via a +//! compile-time computed index (the template parameter). +template <int> +struct IndexedPlacedPlaceholder; + +//! Creates specializations of IndexedPlacedPlaceholder. +//! \param INDEX the integer value which is specialized +//! \param INDEX_P_1 "index plus one"; if INDEX_P_1 equals 1, then the member +//! value will be std::placeholders::_1, etc. +#define SPECIALIZE_INDEXEDPLACEHOLDER(INDEX, INDEX_P_1) \ + template <> \ + struct IndexedPlacedPlaceholder<(INDEX)> { \ + static const decltype(std::placeholders::_##INDEX_P_1) value; \ + } + +// Create specializations up to the tenth placeholder +SPECIALIZE_INDEXEDPLACEHOLDER(0, 1); +SPECIALIZE_INDEXEDPLACEHOLDER(1, 2); +SPECIALIZE_INDEXEDPLACEHOLDER(2, 3); +SPECIALIZE_INDEXEDPLACEHOLDER(3, 4); +SPECIALIZE_INDEXEDPLACEHOLDER(4, 5); +SPECIALIZE_INDEXEDPLACEHOLDER(5, 6); +SPECIALIZE_INDEXEDPLACEHOLDER(6, 7); +SPECIALIZE_INDEXEDPLACEHOLDER(7, 8); +SPECIALIZE_INDEXEDPLACEHOLDER(8, 9); +SPECIALIZE_INDEXEDPLACEHOLDER(9, 10); + +#undef SPECIALIZE_INDEXEDPLACEHOLDER + +// Note: The call sequence is easyBind() -> easyBind_inner() -> +// easyBind_innermost(). + +template <int... Indices, typename Object, typename ReturnType, + typename... Args> +std::function<ReturnType(Args...)> easyBind_innermost( + ReturnType (Object::*method)(Args...), Object& obj) +{ + // std::ref makes sure that obj is not copied. + return std::bind(method, std::ref(obj), + IndexedPlacedPlaceholder<Indices>::value...); +} + +template <int... Indices, typename Object, typename ReturnType, + typename... Args> +std::function<ReturnType(Args...)> easyBind_innermost( + ReturnType (Object::*method)(Args...) const, Object const& obj) +{ + // std::cref makes sure that obj is not copied. + return std::bind(method, std::cref(obj), + IndexedPlacedPlaceholder<Indices>::value...); +} + +template <int... Indices, typename Object, typename ReturnType, + typename... Args> +std::function<ReturnType(Args...)> easyBind_innermost( + ReturnType (Object::*method)(Args...) const, Object& obj) +{ + // std::cref makes sure that obj is not copied. + return std::bind(method, std::cref(obj), + IndexedPlacedPlaceholder<Indices>::value...); +} + +template <int... Indices, typename Object, typename MethodClass, + typename ReturnType, typename... Args> +std::function<ReturnType(Args...)> easyBind_innermost( + ReturnType (MethodClass::*method)(Args...), Object&& obj) +{ + return std::bind(method, std::forward<Object>(obj), + IndexedPlacedPlaceholder<Indices>::value...); +} + +template <int... Indices, typename Object, typename MethodClass, + typename ReturnType, typename... Args> +std::function<ReturnType(Args...)> easyBind_innermost( + ReturnType (MethodClass::*method)(Args...) const, Object&& obj) +{ + return std::bind(method, std::forward<Object>(obj), + IndexedPlacedPlaceholder<Indices>::value...); +} + +template <int... Indices, typename Object, typename MethodClass, + typename ReturnType, typename... Args> +std::function<ReturnType(Args...)> easyBind_inner( + ReturnType (MethodClass::*method)(Args...), Object&& obj, + IntegerSequence<Indices...>) +{ + return easyBind_innermost<Indices...>(method, std::forward<Object>(obj)); +} + +template <int... Indices, typename Object, typename MethodClass, + typename ReturnType, typename... Args> +std::function<ReturnType(Args...)> easyBind_inner( + ReturnType (MethodClass::*method)(Args...) const, Object&& obj, + IntegerSequence<Indices...>) +{ + return easyBind_innermost<Indices...>(method, std::forward<Object>(obj)); +} + +/*! Deduces the signature of the call operator of class \c T. + * + * The matching type of std::function is provided as the member type + * \c FunctionType. + * + * \see http://stackoverflow.com/a/7943765 + */ +template <typename T> +struct FunctionTraits + : public FunctionTraits<decltype(&std::decay<T>::type::operator())> { +}; + +template <typename Object, typename ReturnType, typename... Args> +struct FunctionTraits<ReturnType (Object::*)(Args...)> { + using FunctionType = std::function<ReturnType(Args...)>; +}; + +template <typename Object, typename ReturnType, typename... Args> +struct FunctionTraits<ReturnType (Object::*)(Args...) const> { + using FunctionType = std::function<ReturnType(Args...)>; +}; + +} // namespace detail + +/*! Convenience wrapper for std::bind(). + * + * This function binds the member function pointer \c member of class \c Object + * to the instance \c obj of this class and wraps the result in a std::function + * with matching signature. + * + * The result of this function can be used, e.g., to deduce the signature of the + * \c method (which is not possible with the result of std::bind). + * + * Example: + * \code{.cpp} + * using std::placeholders; + * Object some_object; + * + * auto f_bind = std::function<ReturnType(Arg1, Arg2, Arg3>( + * std::bind(&Object::methodWithThreeArguments, + * std::ref(some_object), _1, _2, _3)); + * + * auto f_easy = easyBind(&Object::methodWithThreeArguments, some_object); + * \endcode + * + * In the example the expressions creating \c f_bind and \c f_easy are + * equivalent. + * + * \note + * There is one difference between the behaviour of std::bind and the one of + * easyBind: In easyBind \c obj is never copied, instead it will be referenced. + * This is in contrast to the behaviour of std::bind, and has been chosen in + * order to prevent accidental copies. + */ +template <typename Object, typename MethodClass, typename ReturnType, + typename... Args> +typename std::enable_if< + std::is_same<MethodClass, + /* Note: All of remove_cv, remove_pointer and decay is + * necessary, e.g. if method is a member function pointer. */ + typename std::remove_cv<typename std::remove_pointer< + typename std::decay<Object>::type>::type>::type>::value, + std::function<ReturnType(Args...)>>::type +easyBind(ReturnType (MethodClass::*method)(Args...), Object&& obj) +{ + return detail::easyBind_inner( + method, std::forward<Object>(obj), + typename GenerateIntegerSequence<sizeof...(Args)>::type{}); +} + +//! \overload +template <typename Object, typename MethodClass, typename ReturnType, + typename... Args> +typename std::enable_if< + std::is_same<MethodClass, + typename std::remove_cv<typename std::remove_pointer< + typename std::decay<Object>::type>::type>::type>::value, + std::function<ReturnType(Args...)>>::type +easyBind(ReturnType (MethodClass::*method)(Args...) const, Object&& obj) +{ + return detail::easyBind_inner( + method, std::forward<Object>(obj), + typename GenerateIntegerSequence<sizeof...(Args)>::type{}); +} + +//! Wraps a callable object in a std::function. +//! +//! This method is provided for convenience since it automatically deduces the +//! correct type of std::function. +template <typename Object> +typename detail::FunctionTraits<Object>::FunctionType easyBind(Object&& obj) +{ + return BaseLib::easyBind(&std::decay<Object>::type::operator(), + std::forward<Object>(obj)); +} + +} // namespace BaseLib + +#endif // BASELIB_FUNCTIONAL_H diff --git a/BaseLib/TMPUtil.h b/BaseLib/TMPUtil.h new file mode 100644 index 0000000000000000000000000000000000000000..7fc6c70173d62b4585ea426d82bb046dc3b87067 --- /dev/null +++ b/BaseLib/TMPUtil.h @@ -0,0 +1,46 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#ifndef BASELIB_TMPUTIL_H +#define BASELIB_TMPUTIL_H + +namespace BaseLib +{ +//! Has sequence of integers as template parameters +template <int...> +struct IntegerSequence { +}; + +//! Generates an IntegerSequence. +//! +//! \see http://stackoverflow.com/a/7858971 +template <int N, int... S> +struct GenerateIntegerSequence { + // effectively pushes N-1 from the left to the list int... S of integers. + typedef typename GenerateIntegerSequence<N - 1, N - 1, S...>::type type; +}; + +template <int... S> +struct GenerateIntegerSequence<0, S...> { + typedef IntegerSequence<S...> type; +}; +/* The template metaprogram proceeds in the following way: + * + * GenerateIntegerSequence<sizeof...(Args)>::type + * + * Assume sizeof...(Args) == 3. Let GIS := GenerateIntegerSequence + * GIS<3, []> + * -> GIS<2, [2]> + * -> GIS<1, [1, 2]> + * -> GIS<0, [0, 1, 2], which has member typedef IntegerSequence<0, 1, 2> + */ + +} // namespace BaseLib + +#endif // BASELIB_TMPUTIL_H diff --git a/Tests/BaseLib/TestFunctional.cpp b/Tests/BaseLib/TestFunctional.cpp new file mode 100644 index 0000000000000000000000000000000000000000..763359c1eca54bdcf36d3ca48a244e03bd17b80a --- /dev/null +++ b/Tests/BaseLib/TestFunctional.cpp @@ -0,0 +1,242 @@ +/** + * \copyright + * Copyright (c) 2012-2016, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include <gtest/gtest.h> +#include <logog/include/logog.hpp> + +#include "BaseLib/Functional.h" + +class InstanceCounter +{ +public: + InstanceCounter() { + ++_num_constructed; + } + InstanceCounter(InstanceCounter const&) { + ++_num_copied; + } + InstanceCounter(InstanceCounter&&) { + ++_num_moved; + } + virtual ~InstanceCounter() { + ++_num_destroyed; + } + + static int getNumberOfConstructions() { return _num_constructed; } + static int getNumberOfCopies() { return _num_copied; } + static int getNumberOfMoves() { return _num_moved; } + static int getNumberOfInstances() + { + return _num_constructed + _num_moved + _num_copied - _num_destroyed; + } + + static void update(int& num_const, int& num_move, int& num_copy, int& num_inst) + { + num_const = getNumberOfConstructions(); + num_move = getNumberOfMoves(); + num_copy = getNumberOfCopies(); + num_inst = getNumberOfInstances(); + } + +private: + static int _num_constructed; + static int _num_copied; + static int _num_moved; + static int _num_destroyed; +}; + +int InstanceCounter::_num_constructed = 0; +int InstanceCounter::_num_copied = 0; +int InstanceCounter::_num_moved = 0; +int InstanceCounter::_num_destroyed = 0; + +class A : public InstanceCounter +{ +public: + A(const double value) : _value(value) {} + A(A const& other) : InstanceCounter(other), _value(other._value) {} + A(A&& other) : InstanceCounter(std::move(other)), _value(other._value) {} + A& operator=(A const& other) { _value = other._value; return *this; } + A& operator=(A&& other) { _value = other._value; return *this; } + + void add(A const& other) { _value += other._value; } + // pass by value intended. + void multiply(A other) { + _value *= other._value; + other._value = 0.0; + } + double getValue() const { return _value; } + double& getValueRef() { return _value; } + double operator()(double const x) { return _value*x; } + +private: + double _value; +}; + +#define EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst) \ + EXPECT_EQ((num_const), InstanceCounter::getNumberOfConstructions()); \ + EXPECT_EQ((num_move), InstanceCounter::getNumberOfMoves()); \ + EXPECT_EQ((num_copy), InstanceCounter::getNumberOfCopies()); \ + EXPECT_EQ((num_inst), InstanceCounter::getNumberOfInstances()) + + +TEST(BaseLib, Functional) +{ + auto num_const = InstanceCounter::getNumberOfConstructions(); + auto num_move = InstanceCounter::getNumberOfMoves(); + auto num_copy = InstanceCounter::getNumberOfCopies(); + auto num_inst = InstanceCounter::getNumberOfInstances(); + + // Base line: measure how many copies and moves + // std::function<>(std::bind(...)) needs. + A a_base(0.0); + + // move the object to std::bind() + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + std::function<void(A)> fct_mult( + std::bind(&A::multiply, std::move(a_base), std::placeholders::_1)); + auto const num_copy_base_move = + InstanceCounter::getNumberOfCopies() - num_copy; + auto const num_move_base_move = + InstanceCounter::getNumberOfMoves() - num_move; + + // call std::function using pass-by-value + A a_base2(0.0); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + fct_mult(a_base2); + auto const num_copy_base_pass = + InstanceCounter::getNumberOfCopies() - num_copy; + auto const num_move_base_pass = + InstanceCounter::getNumberOfMoves() - num_move; + // end base line + + // self test + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + A a1(3.0); + A a2(a1); + EXPECT_INSTANCES(num_const+1, num_move, num_copy+1, num_inst+2); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + auto f1_get = BaseLib::easyBind(&A::getValue, a1); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(3.0, f1_get()); + + // check that really a reference is returned + { + auto f2_getRef = BaseLib::easyBind(&A::getValueRef, a2); + auto& value_ref = f2_getRef(); + EXPECT_EQ(3.0, value_ref); + value_ref = 4.0; + EXPECT_EQ(4.0, a2.getValue()); + } + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + + // test binding to pointers + { + A* ap = &a1; + auto fp_get = BaseLib::easyBind(&A::getValue, ap); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(3.0, fp_get()); + + A const* apc = &a1; + auto fpc_get = BaseLib::easyBind(&A::getValue, apc); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(3.0, fpc_get()); + } + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + + // check that referenced objects are not copied + { + A& a3 = a2; + auto f3_get = BaseLib::easyBind(&A::getValue, a3); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(4.0, f3_get()); + } + { + A const& a3 = a2; + auto f3_get = BaseLib::easyBind(&A::getValue, a3); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(4.0, f3_get()); + } + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + + // temporaries must be moved + { + auto ftemp_get = BaseLib::easyBind(&A::getValue, A(5.0)); + + EXPECT_INSTANCES(num_const + 1, num_move + num_move_base_move, + num_copy + num_copy_base_move, num_inst + 1); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + EXPECT_EQ(5.0, ftemp_get()); + } + // ftemp_get destroyed + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst-1); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + // testing explicit move + { + A a_move(5.0); + EXPECT_INSTANCES(num_const+1, num_move, num_copy, num_inst+1); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + auto ftemp_get = BaseLib::easyBind(&A::getValue, std::move(a_move)); + + EXPECT_INSTANCES(num_const, num_move + num_move_base_move, + num_copy + num_copy_base_move, num_inst+1); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + EXPECT_EQ(5.0, ftemp_get()); + } + // ftemp_get destroyed and a_move + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst-2); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + // test binding a callable object + { + auto f1_op = BaseLib::easyBind(a1); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(21.0, f1_op(7.0)); + } + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + + // test binding a lambda + { + double value = 2.0; + auto f_op = BaseLib::easyBind([&value](const double x) { + value *= x; + return value; + }); + EXPECT_EQ(6.0, f_op(3.0)); + EXPECT_EQ(6.0, value); + } + + // check that parameters passed by reference are not copied + { + auto f1_add = BaseLib::easyBind(&A::add, a1); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + f1_add(a2); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + EXPECT_EQ(7.0, f1_get()); + } + + // check that parameters passed by value are copied + { + auto f1_mult = BaseLib::easyBind(&A::multiply, a1); + EXPECT_INSTANCES(num_const, num_move, num_copy, num_inst); + f1_mult(a2); + + EXPECT_INSTANCES(num_const, num_move + num_move_base_pass, + num_copy + num_copy_base_pass, num_inst); + InstanceCounter::update(num_const, num_move, num_copy, num_inst); + + EXPECT_EQ(28.0, f1_get()); + EXPECT_EQ(4.0, a2.getValue()); + } +}