1

I have a class that wraps any type and only allows operations on that type via a specific function. This helps in many cases where I need control of the flow, synchronization and more.

So I have a simple template class called ObjectWrapper that exposes 1 UseMe method, to allow working with the inner object.

For example if I need to print every time this object is used I can have the following code:

template <typename Object>
class ObjectWrapper
{
public:
    template <typename Functor>
    typename std::result_of<Functor(Object&)>::type UseMe(const Functor& functor)
    {
        std::cout << "Someone is using me!" << std::endl;
        return functor(m_object);
    }

private:
    Object m_object;
};

And I can use it like this:

void main()
{
    mySpecialString.UseMe([](std::string& innerObject) {
        innerObject = "My Special string";
    });

    mySpecialString.UseMe([](std::string& innerObject) {
        std::cout << innerObject << std::endl;
    });

    size_t length = mySpecialString.UseMe([](std::string& innerObject) {
        return innerObject.size();
    });

    std::cout << length << std::endl;
}

which will print:

Someone is using me!
Someone is using me!
My Special string
Someone is using me!
17

This is exactly what I want but it has 1 flaw which is that developers can write the following:

void main()
{
    ObjectWrapper<std::string> mySpecialString;
    mySpecialString.UseMe([](std::string innerObject) {
        innerObject = "My Special string";
    });

    mySpecialString.UseMe([](std::string innerObject) {
        std::cout << innerObject << std::endl;
    });
    size_t length = mySpecialString.UseMe([](std::string innerObject) {
        return innerObject.size();
    });

    std::cout << length << std::endl;
}

which will print:

Someone is using me!
Someone is using me!

Someone is using me!
0

[For those who missed it the string is passed by value to the lambda instead of by ref as I intended]

I thought that the typename std::result_of<Functor(Object&)>::typ will help enforce passing the parameter by ref, but it doesn't.

I also though of using static_assert but can't figure out how to get the type of the passed parameter.

How can I force the users of this ObjectWrapper to get the templated Object by reference only?

ZivS
  • 2,094
  • 2
  • 27
  • 48
  • Accept an std::function rather than a generic function object. – n. m. could be an AI Aug 07 '16 at 08:35
  • How would you return any type using std function? – ZivS Aug 07 '16 at 10:12
  • 1
    `template T UseMe(std::function fun) { ... }` – n. m. could be an AI Aug 07 '16 at 10:46
  • But then you change the API since you must use the`UseMe` with a template type `UseMe(...)` and I wish to avoid that if possible – ZivS Aug 07 '16 at 10:57
  • @ZivS - what about passing a pointer to `m_object`? (I mean: `return functor(&m_object);`) – max66 Aug 07 '16 at 11:42
  • hmm you are right, this is another template type deduction gotcha. Not only that, but even if you use `UseMe`, it stil accepts a lambda that takes its argument by value. Back to drawing board! – n. m. could be an AI Aug 07 '16 at 11:42
  • @max66 - I don't like working with pointer when I don't have to. In addition I think passing a pointer to a member is bad design that could lead to some undesired results from the user's side. Furthermore - passing it by pointer will enforce the user the dereference every time, which I want to avoid – ZivS Aug 07 '16 at 11:46
  • 1
    @ZivS - I see... well, I don't like pointers either – max66 Aug 07 '16 at 12:03

1 Answers1

1

I combined the traits given here and here to find out whether the supplied callable has Object& as its argument:

#include <iostream>
#include <utility>
#include <functional>


template<class> struct rm_func_cv; // undefined
template<class R, class C, class... ArgTypes>
struct rm_func_cv<R(C::*)(ArgTypes...)>  { using type = R(C::*)(ArgTypes...); };
template<class R, class C, class... ArgTypes>
struct rm_func_cv<R(C::*)(ArgTypes...) const>  { using type = R(C::*)(ArgTypes...); };
template<class R, class C, class... ArgTypes>
struct rm_func_cv<R(C::*)(ArgTypes...) volatile>  { using type = R(C::*)(ArgTypes...); };
template<class R, class C, class... ArgTypes>
struct rm_func_cv<R(C::*)(ArgTypes...) const volatile>  { using type = R(C::*)(ArgTypes...); };

namespace {
    // build R (*)(Args...) from R (Args...)
    // compile error if signature is not a valid function signature
    template <typename, typename>
    struct build_free_function;

    template <typename F, typename R, typename ... Args>
    struct build_free_function<F, R (Args...)>
    { using type = R (*)(Args...); };

    // build R (C::*)(Args...) from R (Args...)
    //       R (C::*)(Args...) const from R (Args...) const
    //       R (C::*)(Args...) volatile from R (Args...) volatile
    // compile error if signature is not a valid member function signature
    template <typename, typename>
    struct build_class_function;

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...)>
    { using type = R (C::*)(Args...); };

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...) const>
    { using type = R (C::*)(Args...) const; };

    template <typename C, typename R, typename ... Args>
    struct build_class_function<C, R (Args...) volatile>
    { using type = R (C::*)(Args...) volatile; };

    // determine whether a class C has an operator() with signature S
    template <typename C, typename S>
    struct is_functor_with_signature
    {
        static bool constexpr value  = std::is_same<typename build_class_function<C, S>::type, typename rm_func_cv<decltype(&C::operator())>::type>::value;
    };

    // determine whether a free function pointer F has signature S
    template <typename F, typename S>
    struct is_function_with_signature
    {
        // check whether F and the function pointer of S are of the same
        // type
        static bool constexpr value = std::is_same<
            F, typename build_free_function<F, S>::type
        >::value;
    };

    // C is a class, delegate to is_functor_with_signature
    template <typename C, typename S, bool>
    struct is_callable_impl
        : std::integral_constant<
            bool, is_functor_with_signature<C, S>::value
          >
    {};

    // F is not a class, delegate to is_function_with_signature
    template <typename F, typename S>
    struct is_callable_impl<F, S, false>
        : std::integral_constant<
            bool, is_function_with_signature<F, S>::value
          >
    {};
}

// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
    : is_callable_impl<
        Callable, Signature,
        std::is_class<Callable>::value
      >
{};

template <typename Object>
class ObjectWrapper
{
public:
    template <typename Functor>
    auto UseMe(const Functor& functor)
    {
        using R = decltype(functor(m_object));
        static_assert(is_callable<std::decay_t<Functor>, R(Object&)>::value, "signature must be Object&");
        return functor(m_object);
    }

private:
    Object m_object;
};


void fun(std::string& arg)
{
    std::cout << "fun: " << arg << std::endl;
}

int main()
{
    ObjectWrapper<std::string> mySpecialString;

    mySpecialString.UseMe(fun);


    mySpecialString.UseMe([](std::string& innerObject) {
        innerObject = "My Special string";
    });

    mySpecialString.UseMe([](std::string& innerObject) {
        std::cout << innerObject << std::endl;
    });

    size_t length = mySpecialString.UseMe([](std::string& innerObject) {
        return innerObject.size();
    });

    std::cout << length << std::endl;
}

live example

Community
  • 1
  • 1
m.s.
  • 16,063
  • 7
  • 53
  • 88
  • wohha seems like an overkill for this, but looks like it's working. I'll read the other s.o post to understand what's going on here. Thanks – ZivS Aug 07 '16 at 12:44