Take the 2-minute tour ×
Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

I've created this scheme to preserve function calls until all the arguments are available. The stub_op classes will be replaced with classes that implement a forward-like mechanism that receives notifications when a forward is finished.

I wanted to have a way to set up a function call that turned into a forward that was complete as soon as the function's arguments were available.

So I came up with this. Could this be done more simply?

#include <functional>
#include <memory>
#include <type_traits>
#include <tuple>

struct base_stub_op {
   typedef ::std::shared_ptr<base_stub_op> ptr_t;

   virtual ~base_stub_op() noexcept(true) = default;
};

template <typename ResultType>
struct stub_op : public base_stub_op {
   typedef base_stub_op::ptr_t base_ptr_t;
   typedef ::std::shared_ptr<stub_op<ResultType> > ptr_t;
   typedef ResultType result_type;

   virtual ResultType result() const { return ResultType(); }

   static ptr_t create() {
      return ::std::make_shared<stub_op<ResultType>>();
   }
};

template <typename ResultType>
struct stub_const_op : public stub_op<ResultType> {
   typedef ::std::shared_ptr<stub_const_op<ResultType> > ptr_t;
   typedef ResultType result_type;

   explicit stub_const_op(ResultType &&val)
        : val_(::std::move(val))
   { }
   explicit stub_const_op(const ResultType &val)
        : val_(val)
   { }

   ResultType result() const { return val_; }

   static ptr_t create(ResultType &&val) {
      return ::std::make_shared<stub_const_op<ResultType>>(::std::move(val));
   }
   static ptr_t create(const ResultType &val) {
      return ::std::make_shared<stub_const_op<ResultType>>(val);
   }

 private:
   const ResultType val_;
};

template <typename ResultType>
struct stub_func_op : public stub_op<ResultType> {
   typedef ::std::shared_ptr<stub_func_op<ResultType> > ptr_t;
   typedef ResultType result_type;
   typedef ::std::function<ResultType()> func_t;

   explicit stub_func_op(func_t &&func)
        : func_(::std::move(func))
   { }
   explicit stub_func_op(const func_t &func)
        : func_(func)
   { }
   virtual ~stub_func_op() noexcept(true) { }

   ResultType result() const { return func_(); }

   static ptr_t create(func_t &&func) {
      return ::std::make_shared<stub_func_op<ResultType>>(::std::move(func));
   }
   static ptr_t create(const func_t &func) {
      return ::std::make_shared<stub_func_op<ResultType>>(func);
   }

 private:
   const func_t func_;
};

template <typename T>
struct is_op_ptr {
 private:
   // Returns false_type, which has a ::value that is false.
   template <class AT>
   static constexpr std::false_type is_it_a_ptr(...);

   // Returns true_type (if enable_if allows it to exist).
   template <class AT>
   static constexpr typename ::std::enable_if<
      ::std::is_same<
         AT,
         typename stub_op<typename AT::element_type::result_type>::ptr_t>::value,
      std::true_type>::type  // note the true_type return
   is_it_a_ptr(int); // no definition needed

 public:
   // do everything unevaluated
   static constexpr bool value = decltype(is_it_a_ptr<T>(0))::value;
};

template <typename T>
class transform_type
{
 public:
   static constexpr bool passthrough = is_op_ptr<T>::value;
   typedef typename ::std::conditional< passthrough,
                                        T,
                                        typename stub_op<T>::ptr_t>::type type;
   typedef T orig_type;
   typedef decltype(::std::declval<type>()->result()) base_type;

   transform_type(const type &o) : wrapped_(o) { }
   transform_type(type &&o) : wrapped_(o) { }

   template <typename U = T>
   typename ::std::enable_if< ::std::is_same<U, T>::value && passthrough,
                              orig_type>::type
   result() {
      return wrapped_;
   }
   template <typename U = T>
   typename ::std::enable_if< ::std::is_same<U, T>::value && !passthrough,
                              orig_type>::type
   result() {
      return wrapped_->result();
   }

 private:
   type wrapped_;
};

template <typename ResultType, typename FuncT, typename TupleT>
class suspended_call {
 public:
   explicit suspended_call(FuncT func, TupleT args)
        : func_(::std::move(func)), args_(::std::move(args))
   {
   }

   // This can only be called once and will alter the state of the object so it
   // cannot be called again.
   ResultType operator()() {
      typedef call_helper< ::std::tuple_size<TupleT>::value> helper_t;
      return ::std::move(helper_t::engage(func_, args_));
   }

 private:
   FuncT func_;
   TupleT args_;

   template <unsigned int N, unsigned int... I>
   struct call_helper {
      static ResultType engage(FuncT &func, TupleT &args) {
         return ::std::move(call_helper<N - 1, N - 1, I...>::engage(func, args));
      }
   };
   template <unsigned int... I>
   struct call_helper<0, I...> {
      static ResultType engage(FuncT &func, TupleT &args) {
         return ::std::move(func(::std::get<I>(args).result()...));
      }
   };
};

template <typename ResultType, typename... ArgTypes>
class deferred {
 public:
   typedef typename stub_op<ResultType>::ptr_t deferred_t;
   typedef ::std::function<ResultType(ArgTypes...)> wrapped_func_t;

   explicit deferred(const wrapped_func_t &func)
        : func_(func)
   {
   }

   deferred_t until(const typename transform_type<ArgTypes>::type &... args) {
      typedef ::std::tuple<transform_type<ArgTypes>...> argtuple_t;
      argtuple_t saved_args = ::std::make_tuple(args...);
      ::std::function<ResultType()> f{suspended_call<ResultType, wrapped_func_t, argtuple_t>(func_, ::std::move(saved_args))};
      return stub_func_op<ResultType>::create(f);
   }

 private:
   const wrapped_func_t func_;
};

template <typename ResultType, typename... ArgTypes>
deferred<ResultType, ArgTypes...>
defer(::std::function<ResultType(ArgTypes...)> func)
{
   return deferred<ResultType, ArgTypes...>(func);
}

template <typename ResultType, typename... ArgTypes>
deferred<ResultType, ArgTypes...>
defer(ResultType (*func)(ArgTypes...))
{
   ::std::function<ResultType(ArgTypes...)> f = func;
   return deferred<ResultType, ArgTypes...>(::std::move(f));
}

Example use:

#include <sparkles/make_operation.hpp>
#include <iostream>

using ::std::cerr;

int a_function()
{
   cerr << "In a_function.\n";
   return 5;
}

int a_function2(int arg)
{
   cerr << "In a_function(" << arg << ").\n";
   return arg;
}

int main()
{
   cerr << "Here 1\n";
   auto func1 = defer(a_function).until();
   cerr << "Here 2\n";
   auto func2 = defer(a_function2).until(func1);
   cerr << "Here 3\n";
   cerr << "func2->result() == " << func2->result() << '\n';
}

The thing that this is actually eventually going to become a part of is called Sparkles, and it's GPLv3, so the source code is there.

share|improve this question
    
Why out of my league. There are not that many C++ reviewers here (and fewer that I have seen have this kind of knowledge). You may want to try pinging somebody from this list to give you some help: stackoverflow.com/tags/c%2b%2b/topusers –  Loki Astari Jan 20 '13 at 15:43
    
@LokiAstari: I've done a bit of that, mostly by poking <Lounge C++>, but I'll try some more. Thanks! –  Omnifarious Jan 20 '13 at 18:17
add comment

1 Answer

the very first and important question: what problem you are trying to solve with this code? Why do you think it's better than simple:

auto call_chain = []()
{
    return a_function2(a_function());
};
std::cerr << "result == " << call_chain() << std::endl;

your code doesn't allow to do smth in the middle of function calls -- i.e. smth like this:

  • call a_function()
  • do smth while results available
  • when result ready, pass it to a_function2()
  • do smth else
  • wait/check for final result

so, personally I see no reason to use your code (at least in it's current state)...

anyway, some (not/less important) notes about your code:

  • do not use ::std::smth everywhere, std::smth quite enough
  • put your code into your own namespace. put everything what is not a public API into your_ns::details namespace
  • use inline for your template functions in a header to avoid 'function redeclaration' errors
  • use override for result() in stub_op child classes
  • why to use shared_ptr, why not unique_ptr?
  • rethink semantic of your service classes and disable all not required (undesirable) ctors/assign operators for them to avoid use cases that shouldn't work
  • currently defer() function won't work w/ labmdas and any user provided functors. to implement it in a better way, define it as template <typename Func> and then you have to analyze that Func is a callable type (using boost::function_types or smth similar). cuz C++11 have no concepts, you may use static_assert with human readable message sayin that defer should be instantiated w/ callable types only...
  • no need to override a function w/ T&&+std::move and const T& parameters. use T&& and std::forward instead.
share|improve this answer
    
The library this will be incorporated in has something called a remote_operation which puts a notification on an inter-thread queue when it's done. A remote_operation is a type of operation. The stub_op class in the code above is a stand-in for the more complex operation class. While I was working on this, I didn't want to have to deal with the complexity of that class. Also, see this SO question to explain my stylistic use of ::std everywhere: stackoverflow.com/questions/1661912/… –  Omnifarious Jan 26 '13 at 21:02
    
+1 for the inline suggestion. Though most compilers nowadays emit 'weak' bindings for template expansions so there can be multiples with no complaints. –  Omnifarious Jan 26 '13 at 21:04
    
And I would like defer to work with any callable. My overload for function<ResultType(ArgT...)> is a poor attempt at making that happen. –  Omnifarious Jan 26 '13 at 21:05
1  
I used shared_ptr because you might say something like x = defer(...).until(foo); y = defer(...).until(foo);. Poof, now you have two things depending on the same object's results. That object can't go away until both dependencies have fetched their results. –  Omnifarious Jan 26 '13 at 21:08
    
@Omnifarious what kind of notifications do you use? I think (strognly believe) that using boost::signals2 or (possible queue of shared) future/promises (std or boost) it would be possible to do the job simple than your approach. btw, boost allows to wait on multiple futures, in contrast to std. anyway if you are talking about notifications boost::signals2 is a first thing cames into my mind... –  zaufi Jan 26 '13 at 21:16
show 3 more comments

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.