0

I want to have a template function which accepts unary member-function pointers of an instance of some generic type. My problem is that I must support both void(T val) and void(const T& val) member functions.

I have written one template function for each case and it works fine, but this leads to code duplication since the function logic is completely the same. (I found something completely similar here: Function taking both pointer to member-function and pointer to const member-function but I fail to see a definitive solution).

An example of the generic type mentioned above:

using UserAddress = std::string;

class User
{
private:
    int mHeight;
    UserAddress mAddress;
public:
    void SetHeight(int height){mHeight = height;}
    void SetAddress(const UserAddress& address){mAddress = address;}
};

Where UserAddress is some heavy type I want to pass by reference.

My templated function:

template <typename TPersistentObject>
class Persistence
{
private:
    std::map<std::string, std::function<void(User*)>> mSetterOfProperty;

    template <typename TPersistentObject, typename TPropertyValue>
    void DefinePropertySettingMethod(const std::string& propertyName,
                                     void (TPersistentObject::*propertySetter)(TPropertyValue), std::function<TPropertyValue(void)> dataReader)
    {
            mSetterOfProperty[propertyName] =
                [propertySetter, columnDataReader](TPersistentObject* persistentObject) 
            {
                (persistentObject->*propertySetter)(dataReader());
            };
    }
};

/// Const& implementation leading to code duplication
    template <typename TPersistentObject, typename TPropertyValue>
    void DefinePropertySettingMethod(const std::string& propertyName,
                                     void (TPersistentObject::*propertySetter)(const TPropertyValue&), std::function<TPropertyValue(void)> dataReader)
    {
...
    }
};

Is there some way to define this function to support the following:


int main()
{
    
    auto  intDataReader = []() {
        return 1;
    };
    
    auto  stringDataReader = []() {
        return UserAddress("Next Door");
    };
    
  Persistence p;
  p.DefinePropertySettingMethod<User,int>("Height", &User::SetHeight, intDataReader);
  p.DefinePropertySettingMethod<User,UserAddress>("Address", &User::SetAddress, stringDataReader);
   
}
Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
georanto
  • 111
  • 11
  • [This close approximation](https://rextester.com/ICEAGZ66608) to your code compiles with just the first overload. It's not clear why you feel you need both. – Igor Tandetnik Oct 12 '19 at 03:36
  • I hadn't included a third parameter on the function. I am sorry. I have modified the code. Now you should be able to get the error. I also see the problem now (the const& substitution on the type of the third parameter), but the question still stands. Maybe some trait (I am not familiar with traits). – georanto Oct 12 '19 at 17:15
  • Have two separate template parameters in place of `TPropertyValue` - one for setter, one for getter. Allow them to be deduced independently. – Igor Tandetnik Oct 12 '19 at 17:52
  • This worked nicely (I don't even need the explicit type declarations now). Thank you!. However it loosens the type dependency between the type passed to the setter function and the type returned from the dataReader. In the function I invoke the dataReader and I pass its return value in the setter. The compiler will complain if there is a type mismatch but for clarity reasons, I'd like to have i this dependency expressed on the function prototype. – georanto Oct 13 '19 at 06:47
  • You simply can static_assert on a type mismatch... – Klaus Oct 13 '19 at 07:36
  • You could use `std::enable_if`, checking that the two types are the same after references and cv-qualifiers are removed. An appropriate combination of `std::is_same`, `std::decay` et al is left as an exercise for the reader. – Igor Tandetnik Oct 13 '19 at 14:54

1 Answers1

0

Thanks to Igor Tandetnik 's tip I managed to compile a solution. std::enable_if is not what I needed though since I did not need to deactivate an overload (or at least I couldn't come to a solution using it). std::conditional did the trick.

Here is the code:

#include <string>
#include <functional>
#include <map>
#include <string>
#include <type_traits>

using UserAddress = std::string;

class User
{
private:
    int mHeight;
    UserAddress mAddress;
public:
    void SetHeight(int height){mHeight = height;}
    void SetAddress(const UserAddress& address){mAddress = address;}
};

template <typename TPersistentObject>
class Persistence
{
public:
    std::map<std::string, std::function<void(TPersistentObject*)>> mSetterOfProperty;

    template <typename TPropertyValue>    
    void DefinePropertySettingMethod(const std::string& propertyName,
                                     void (TPersistentObject::*propertySetter)(TPropertyValue), 
                                     std::function<
                                             typename std::conditional<!std::is_same<TPropertyValue, typename  std::decay<TPropertyValue>::type>::value,
                                             typename std::decay<TPropertyValue>::type, TPropertyValue>::type
                                     (void)> dataReader)
    {
            mSetterOfProperty[propertyName] =
                [propertySetter, dataReader](TPersistentObject* persistentObject) 
            {
                (persistentObject->*propertySetter)(dataReader());
            };
    }
};


int main()
{

    std::function<int()>  intDataReader = []() {
        return 1;
    };

    std::function<std::string()>  stringDataReader = []() {
        return UserAddress("Next Door");
    };

  Persistence<User> p;
  p.DefinePropertySettingMethod("Height", &User::SetHeight, intDataReader);
  p.DefinePropertySettingMethod("Address", &User::SetAddress, stringDataReader);

}
georanto
  • 111
  • 11