Skip to content

Instantly share code, notes, and snippets.

@apriori
Created April 10, 2016 20:19
Show Gist options
  • Save apriori/02d09da40bb454078d82b7e669d2f07a to your computer and use it in GitHub Desktop.
Save apriori/02d09da40bb454078d82b7e669d2f07a to your computer and use it in GitHub Desktop.
#ifndef LOCKEDSTREAM_H
#define LOCKEDSTREAM_H
/**
* Tuplegatherer and a LockedStream class as type safe
* wrappers over an ostream with a convenient API
*
*/
#include <iostream>
#include <functional>
#include <tuple>
#include <pthread.h>
/**
* @brief A dumb termination marker class
*/
class FinishMark {};
static FinishMark finish;
/**
* Forward declaration
*/
template<std::ostream* T>
class LockedStream;
/**
* We do need an assymetric type converter
* which does convert T -> std::decay<T> for all but std::string.
* std::string is to be expanded with c_str() and then treated as
* const char*.
*/
template<typename T>
struct decayStr {
typedef typename std::decay<T>::type type;
};
/**
* Template specialization for std::string. type is now const char*
*/
template<>
struct decayStr<std::string> {
typedef const char* type;
};
template<typename T>
/**
* Depending on an input type T, use std::forward.
* If T == std::string, use c_str().
* @brief forwardStr a type converter for decayed types
* @param v
* @return
*/
typename decayStr<const T*>::type forwardStr(const T* v) {
typedef typename decayStr<const T*>::type T1;
return static_cast<T1>(v);
}
template<typename T>
/**
* Depending on an input type T, use std::forward.
* If T == std::string, use c_str().
* @brief forwardStr a type converter for decayed types
* @param v
* @return
*/
typename decayStr<T>::type forwardStr(T& v) {
typedef typename decayStr<T>::type T1;
//return std::forward(v);
return static_cast<T1>(v);
}
template<typename T>
/**
* @brief forwardStr a type converter for decayed types, move variant
* @param v
* @return
*/
typename decayStr<T>::type forwardStr(T&& v) {
return std::forward(v);
}
/**
* @brief forwardStr a type converter for a decayed std::string, move variant
* @return just c_str()
*/
template<>
typename decayStr<std::string>::type forwardStr<std::string>(std::string&);
/**
* @brief forwardStr a type converter for a decayed std::string, move variant
* @return just c_str()
*/
template<>
typename decayStr<std::string>::type forwardStr<std::string>(std::string&&);
template<std::ostream* stream, typename... K>
/**
* @brief The TupleGatherer, a class meant to gather
* a chain of operator<< calls into a tuple, which is then
* commited to the stream in a locked way.
*/
class TupleGatherer {
public:
/**
* @brief TupleGatherer constructor which takes the current tuple
* @param tuple
*/
TupleGatherer(std::tuple<K...> tuple) : tuple(tuple) {}
/**
* Handle the dumb termination marker and start commiting
* the tuple.
* @note This function will still return a TupleGatherer
* with the FinishMark added to the tuple. This TupleGatherer
* has no use.
* @brief operator <<
* @return
*/
TupleGatherer<stream, K..., FinishMark> operator<<(FinishMark&);
template<typename V>
/**
* Add a reference of type V to the internal tuple
* @brief operator <<, similar to ostream::operator<<
* @param v
* @return a new TupleGatherer with an extended internal tuple by v
*/
TupleGatherer<stream, K..., typename decayStr<V>::type> operator<<(V& v);
template<typename V>
/**
* Add a move reference of type V to the internal tuple
* @brief operator <<, similar to ostream::operator<<
* @param v
* @return a new TupleGatherer with an extended internal tuple by v
*/
TupleGatherer<stream, K..., typename decayStr<V>::type> operator<<(V&& v);
private:
template<std::size_t I = 0, typename...Ts>
/**
* Recursive iterating template over a tuple, which commits
* every single entry of the tuple to the stream. This is
* the termination case (I == sizeof...(Ts)).
* @brief commitTuple
* @return
*/
inline typename std::enable_if<I == sizeof...(Ts), void>::type
commitTuple(std::tuple<Ts...>&) {}
template<std::size_t I = 0, typename...Ts>
/**
* Recursive iterating template over a tuple, which commits
* every single entry of the tuple to the stream. This is
* the iterating case (I < sizeof...(Ts)), which recursively unrolls
* with template parameter I until every tuple entry is commited to
* the stream.
* @brief commitTuple
* @return
*/
inline typename std::enable_if<I < sizeof...(Ts), void>::type
commitTuple(std::tuple<Ts...>& tuple);
/**
* the managed current tuple
* @brief tuple
*/
std::tuple<K...> tuple;
};
template<std::ostream* stream, typename... K>
TupleGatherer<stream, K..., FinishMark> TupleGatherer<stream, K...>::operator<<(FinishMark&) {
//lock the stream first
LockedStream<stream>::lock();
commitTuple(tuple);
//always add a line break
*stream << std::endl;
//force flush to make sure we don't get mixed results by the different threads
std::flush(*stream);
LockedStream<stream>::unlock();
//return new TupleGatherer, which is only meant as a dummy in this case.
auto newTuple = std::tuple_cat(tuple, std::make_tuple(finish));
return TupleGatherer<stream, K..., FinishMark>(newTuple);
}
template<std::ostream* stream, typename... K>
template<typename V>
TupleGatherer<stream, K..., typename decayStr<V>::type> TupleGatherer<stream, K...>::operator<<(V& v) {
//typedef to satisfy the interface
typedef typename decayStr<V>::type V1;
//notice that the call to forwardStr is to be infered by type V (and not V1)
auto newTuple = std::tuple_cat(tuple, std::make_tuple(forwardStr(v)));
return TupleGatherer<stream, K..., V1>(newTuple);
}
template<std::ostream* stream, typename... K>
template<typename V>
TupleGatherer<stream, K..., typename decayStr<V>::type> TupleGatherer<stream, K...>::operator<<(V&& v) {
typedef typename decayStr<V>::type V1;
auto newTuple = std::tuple_cat(tuple, std::make_tuple(forwardStr(v)));
return TupleGatherer<stream, K..., V1>(newTuple);
}
template<std::ostream* stream, typename... K>
template<std::size_t I, typename...Ts>
inline typename std::enable_if<I < sizeof...(Ts), void>::type
TupleGatherer<stream, K...>::commitTuple(std::tuple<Ts...>& tuple) {
//get current entry
*stream << std::get<I>(tuple);
//iterate forward
commitTuple<I+1, Ts...>(tuple);
}
template<std::ostream* stream>
/**
* A LockedStream singleton, to be instanced over an ostream pointer (e.g. &std::cout).
* Sample usage:
* @code
* int value = 1
* LOCKEDCOUT() << "This is a test " << value << finish;
* @note The used finish mark in the example is mandatory and will commit the values to the stream.
* @see Aliases CoutRecursiveLocked, CerrRecursiveLocked and static std::functions
* LOCKEDCOUT and LOCKEDCERR
*
* @brief The LockedStream class
*/
class LockedStream {
/**
* We allow TupleGatherer to call private functions so it can use locking, which is never
* meant to be used externally.
*/
template<std::ostream*, typename... K>
friend class TupleGatherer;
public:
LockedStream(LockedStream<stream>&) = delete;
LockedStream(LockedStream<stream>&&) = delete;
LockedStream(const LockedStream<stream>&&) = delete;
LockedStream(const LockedStream<stream>&) = delete;
LockedStream<stream>& operator=(LockedStream<stream>&) = delete;
LockedStream<stream>& operator=(const LockedStream<stream>&) = delete;
LockedStream<stream>& operator=(const LockedStream<stream>&&) = delete;
/**
* Static getInstance method that returns a reference to the initiated singleton.
* If the singleton is not initiated, this function will do this is a mutex locked way.
* @brief getInstance Returns reference to current LockedStream instance
* @return
*/
static LockedStream<stream>& getInstance();
/**
* A clean up function meant for deterministic clean up.
* This is meant to be called by a single process (e.g. main thread) only.
* @brief clean
*/
static void clean();
~LockedStream();
/**
* Handle the dummy finish mark immediately.
* Using this after the first call to operator<< is effectively resulting in a
* NOOP commit to ostream T.
* @brief operator <<
* @return
*/
TupleGatherer<stream, FinishMark> operator<<(FinishMark&);
template<typename V>
/**
* Handle a new value reference and create the first TupleGatherer instance.
* @brief operator << Create TupleGatherer which holds the initial value v
* @param v
* @return TupleGatherer with initial value v;
*/
TupleGatherer<stream, typename decayStr<V>::type> operator<<(V& v);
template<typename V>
/**
* Handle a new value reference and create the first TupleGatherer instance. (move variant)
* @brief operator << Create TupleGatherer which holds the initial value v (move variant)
* @param v
* @return TupleGatherer with initial value v;
*/
TupleGatherer<stream, typename decayStr<V>::type> operator<<(V&& v);
protected:
/**
* private default constructor, because this class is not meant to be
* instantiated publicly.
* @brief LockedStream
*/
LockedStream() = default;
/**
* Use the static mutex to lock on this stream
* @brief lock
*/
static void lock() { pthread_mutex_lock(&mutex); }
/**
* Use the static mutex to unlock on this stream.
* @brief lock
*/
static void unlock() { pthread_mutex_unlock(&mutex); }
/**
* The current static LockedStream instance
* @brief instance
*/
static LockedStream<stream>* instance;
/**
* The static lock mutex
*/
static pthread_mutex_t mutex;
};
template<std::ostream* stream>
LockedStream<stream>* LockedStream<stream>::instance = nullptr;
template<std::ostream* stream>
pthread_mutex_t LockedStream<stream>::mutex = PTHREAD_MUTEX_INITIALIZER;
template<std::ostream* stream>
LockedStream<stream>& LockedStream<stream>::getInstance() {
lock();
if (instance == nullptr) {
instance = new LockedStream<stream>();
}
unlock();
return *instance;
}
template<std::ostream* stream>
void LockedStream<stream>::clean() {
if (instance != nullptr) {
delete instance;
}
pthread_mutex_destroy(&mutex);
}
template<std::ostream* stream>
TupleGatherer<stream, FinishMark> LockedStream<stream>::operator<<(FinishMark&) {
//effectively NOOP
return TupleGatherer<stream, FinishMark>(std::make_tuple(finish));
}
template<std::ostream* stream>
template<typename V>
TupleGatherer<stream, typename decayStr<V>::type> LockedStream<stream>::operator<<(V& v) {
typedef typename decayStr<V>::type V1;
auto tuple = std::make_tuple(forwardStr(v));
return TupleGatherer<stream, V1>(tuple);
}
template<std::ostream* stream>
template<typename V>
TupleGatherer<stream, typename decayStr<V>::type> LockedStream<stream>::operator<<(V&& v) {
typedef typename decayStr<V>::type V1;
auto tuple = std::make_tuple(forwardStr(v));
return TupleGatherer<stream, V1>(tuple);
}
template<std::ostream* stream>
LockedStream<stream>::~LockedStream() {
}
/**
* Some helpful aliases
*/
using CoutRecursiveLocked = LockedStream<&std::cout>;
using CerrRecursiveLocked = LockedStream<&std::cerr>;
static std::function<CoutRecursiveLocked&()> LOCKEDCOUT = &CoutRecursiveLocked::getInstance;
static std::function<CerrRecursiveLocked&()> LOCKEDCERR = &CerrRecursiveLocked::getInstance;
#endif // LOCKEDSTREAM_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment