Skip to content

Instantly share code, notes, and snippets.

@VictorLaskin
Last active July 7, 2021 09:26
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save VictorLaskin/91ab0574bd6a1d0fca88 to your computer and use it in GitHub Desktop.
Save VictorLaskin/91ab0574bd6a1d0fca88 to your computer and use it in GitHub Desktop.
Templates as first-class citizens in C++11
// Templates as first-class citizens in C++11
// Example code from http://vitiy.info/templates-as-first-class-citizens-in-cpp11/
// Written by Victor Laskin (victor.laskin@gmail.com)
#include <iostream>
#include <algorithm>
#include <functional>
#include <vector>
#include <tuple>
#include <memory>
#include <sstream>
using namespace std;
#define LOG cout
#define NL endl
// apply tuple to function...
namespace fn_detail {
template<int ...>
struct int_sequence {};
template<int N, int ...S>
struct gen_int_sequence : gen_int_sequence<N-1, N-1, S...> {};
template<int ...S>
struct gen_int_sequence<0, S...> {
typedef int_sequence<S...> type;
};
template <typename F, typename... Args, int... S>
inline auto fn_tuple_apply(int_sequence<S...>, const F& f, const std::tuple<Args...>& params) -> decltype( f((std::get<S>(params))...)
)
{
return f((std::get<S>(params))...);
}
}
template <typename F, typename... Args> //f(std::declval<Args>()...)
inline auto fn_tuple_apply(const F& f, const std::tuple<Args...>& params) -> decltype( f(std::declval<Args>()...) )
{
return fn_detail::fn_tuple_apply(typename fn_detail::gen_int_sequence<sizeof...(Args)>::type(), f, params);
}
// end of apply tuple
// universal function / extended function wrapper !
template<typename F, typename TPackBefore = std::tuple<>, typename TPackAfter = std::tuple<>>
class fn_universal {
private:
F f; ///< main functor
TPackAfter after; ///< curryed arguments
TPackBefore before; ///< curryed arguments
public:
fn_universal(F && f) : f(std::forward<F>(f)), after(std::tuple<>()), before(std::tuple<>()) {}
fn_universal(const F & f, const TPackBefore & before, const TPackAfter & after) : f(f), after(after), before(before) {}
template <typename... Args>
auto operator()(Args... args) const -> decltype(
fn_tuple_apply(f, std::tuple_cat(before, make_tuple(args...), after))
) {
// execute via tuple
return fn_tuple_apply(f, std::tuple_cat(before, make_tuple(std::forward<Args>(args)...), after));
}
// curry
template <typename T>
auto curry(T && param) const -> decltype(fn_universal<F,decltype(std::tuple_cat(before, std::make_tuple(param))),TPackAfter>(f, std::tuple_cat(before, std::make_tuple(param)), after))
{
return fn_universal<F,decltype(std::tuple_cat(before, std::make_tuple(param))),TPackAfter>(f, std::tuple_cat(before, std::make_tuple(std::forward<T>(param))), after);
}
template <typename T>
auto curry_right(T && param) const -> decltype(fn_universal<F, TPackBefore, decltype(std::tuple_cat(after, std::make_tuple(param)))>(f, before, std::tuple_cat(after, std::make_tuple(param))))
{
return fn_universal<F, TPackBefore, decltype(std::tuple_cat(after, std::make_tuple(param)))>(f, before, std::tuple_cat(after, std::make_tuple(std::forward<T>(param))));
}
};
template <typename F>
auto fn_to_universal(F && f) -> fn_universal<F>
{
return fn_universal<F>(std::forward<F>(f));
}
// left curry by << operator
template<typename UF, typename Arg>
auto operator<<(const UF & f, Arg && arg) -> decltype(f.template curry<Arg>(std::forward<Arg>(arg)))
{
return f.template curry<Arg>(std::forward<Arg>(arg));
}
// right curry by >> operator
template<typename UF, typename Arg>
auto operator>>(const UF & f, Arg && arg) -> decltype(f.template curry_right<Arg>(std::forward<Arg>(arg)))
{
return f.template curry_right<Arg>(std::forward<Arg>(arg));
}
// OBJECT from TEMPLATE function!
// Template as first-class citizen
#define make_citizen(X) class tfn_##X { public: template <typename... Args> auto operator()(Args&&... args) const ->decltype(X(std::forward<Args>(args)...)) { return X(std::forward<Args>(args)...); } }
// tuple concatenation via << operator
template<typename... OldArgs, typename NewArg>
tuple<OldArgs...,NewArg> operator<<(const tuple<OldArgs...> & t, const NewArg& arg)
{
return std::tuple_cat(t, std::make_tuple(arg));
}
template<typename T, class F>
auto operator|(T&& param, const F& f) -> decltype(f(param)) // typename std::result_of<F(T)>::type
{
return f(std::forward<T>(param));
}
template <typename... Args>
void print(Args... args)
{
(void)(int[]){((LOG << args), 0)...}; LOG << NL;
}
make_citizen(print);
// Return count of elements as templated operator
template <typename T>
int count(const T& container)
{
return container.size();
}
make_citizen(count);
/// Universal operators
// MAP
template <typename T, typename... TArgs, template <typename...>class C, typename F>
auto fn_map(const C<T,TArgs...>& container, const F& f) -> C<decltype(f(std::declval<T>()))>
{
using resultType = decltype(f(std::declval<T>()));
C<resultType> result;
for (const auto& item : container)
result.push_back(f(item));
return result;
}
// REDUCE (FOLD)
template <typename TResult, typename T, typename... TArgs, template <typename...>class C, typename F>
TResult fn_reduce(const C<T,TArgs...>& container, const TResult& startValue, const F& f)
{
TResult result = startValue;
for (const auto& item : container)
result = f(result, item);
return result;
}
// FILTER
template <typename T, typename... TArgs, template <typename...>class C, typename F>
C<T,TArgs...> fn_filter(const C<T,TArgs...>& container, const F& f)
{
C<T,TArgs...> result;
for (const auto& item : container)
if (f(item))
result.push_back(item);
return result;
}
#define make_universal(NAME, F) make_citizen(F); const auto NAME = fn_to_universal(tfn_##F());
make_universal(fmap, fn_map);
make_universal(reduce, fn_reduce);
make_universal(filter, fn_filter);
template <typename T, typename... Args>
T sum_impl(T arg, Args... args)
{
T result = arg;
[&result](...){}((result += args, 0)...);
return result;
}
make_universal(sum, sum_impl);
template <typename T, typename TName>
bool isNameEqualImpl(const T& obj, const TName& name)
{
return (obj->name == name);
}
make_universal(isName, isNameEqualImpl);
template <typename T, typename TId>
bool isIdEqualImpl(const T& obj, const TId& id)
{
return (obj->id == id);
}
make_universal(isId, isIdEqualImpl);
template <typename T>
bool isNotNullImpl(const T& t)
{
return (t != nullptr);
}
make_universal(isNotNull, isNotNullImpl);
template <typename F, typename TKey, typename T, typename... TArgs, template <typename...>class C>
T findOneImpl(const C<T,TArgs...>& container, const F& f, const TKey& key)
{
for (const auto& item : container)
if (f(item, key))
return item;
return nullptr;
}
make_universal(ffind, findOneImpl);
// -------------------- chain of functors --------------------->
// The chain of functors ... is actualy just a tuple of functors
template <typename... FNs>
class fn_chain {
private:
const std::tuple<FNs...> functions;
template <std::size_t I, typename Arg>
inline typename std::enable_if<I == sizeof...(FNs) - 1, decltype(std::get<I>(functions)(std::declval<Arg>())) >::type
call(Arg arg) const
{
return std::get<I>(functions)(std::forward<Arg>(arg));
}
template <std::size_t N, std::size_t I, typename Arg>
struct final_type : final_type<N-1, I+1, decltype(std::get<I>(functions)(std::declval<Arg>())) > {};
template <std::size_t I, typename Arg>
struct final_type<0, I, Arg> {
using type = decltype(std::get<I>(functions)(std::declval<Arg>()));
};
template <std::size_t I, typename Arg>
inline typename std::enable_if<I < sizeof...(FNs) - 1, typename final_type<sizeof...(FNs) - 1 - I, I, Arg>::type >::type
call(Arg arg) const
{
return this->call<I+1>(std::get<I>(functions)(std::forward<Arg>(arg)));
}
public:
fn_chain() : functions(std::tuple<>()) {}
fn_chain(std::tuple<FNs...> functions) : functions(functions) {}
// add function into chain
template< typename F >
inline auto add(const F& f) const -> fn_chain<FNs..., F>
{
return fn_chain<FNs..., F>(std::tuple_cat(functions, std::make_tuple(f)));
}
// call whole functional chain
template <typename Arg>
inline auto operator()(Arg arg) const -> decltype(this->call<0,Arg>(arg))
{
return call<0>(std::forward<Arg>(arg));
}
};
// pipe function into functional chain via | operator
template<typename... FNs, typename F>
inline auto operator|(fn_chain<FNs...> && chain, F&& f) -> decltype(chain.add(f))
{
return chain.add(std::forward<F>(f));
}
// ------------------------------- Monads ---------------------------------->
enum class maybe_state { normal, empty };
template <typename T>
typename std::enable_if< std::is_object<decltype(T()) >::value, T>::type
set_empty() { return T(); }
template<> int set_empty<int>() { return 0; }
template<> string set_empty<string>() { return ""; }
template<typename T>
class maybe {
private:
const maybe_state state;
const T x;
template <typename R>
maybe<R> fromValue(R&& result) const
{
return maybe<R>(std::forward<R>(result));
}
template <typename R>
maybe<std::shared_ptr<R>> fromValue(std::shared_ptr<R>&& result) const
{
if (result == nullptr)
return maybe<std::shared_ptr<R>>();
else
return maybe<std::shared_ptr<R>>(std::forward<std::shared_ptr<R>>(result));
}
public:
// monadic return
maybe(T&& x) : x(std::forward<T>(x)), state(maybe_state::normal) {}
maybe() : x(set_empty<T>()), state(maybe_state::empty) {}
// monadic bind
template <typename F>
auto operator()(F f) const -> maybe<decltype(f(std::declval<T>()))>
{
using ResultType = decltype(f(std::declval<T>()));
if (state == maybe_state::empty)
return maybe<ResultType>();
return fromValue(f(x));
}
// extract value
T getOr(T&& anotherValue) const { return (state == maybe_state::empty) ? anotherValue : x; };
};
template<typename T, typename F>
inline auto operator|(maybe<T> && monad, F&& f) -> decltype(monad(f))
{
return monad(std::forward<F>(f));
}
template<typename T, typename TDefault>
inline T operator||(maybe<T> && monad, TDefault&& t)
{
return monad.getOr(std::forward<TDefault>(t));
}
template <typename T>
maybe<T> just(T&& t)
{
return maybe<T>(std::forward<T>(t));
}
string xmlWrapImpl(string name, string item)
{
return "<" + name + ">" + item + "</" + name + ">";
}
make_universal(xmlWrap, xmlWrapImpl);
/// Simple immutable data
class UserData {
public:
const int id;
const string name;
const int parent;
UserData(int id, string name, int parent) : id(id), name(name), parent(parent) {}
};
/// Shared pointer to immutable data
using User = std::shared_ptr<UserData>;
template <class T, class... P>
inline auto make(P&&... args) -> T {
return std::make_shared<typename T::element_type>(std::forward<P>(args)...);
}
int main() {
// let's test citizenship...
tfn_print print;
print(5);
print("hello");
// let's call function by providing tuple
auto f = [](int x, int y, int z) { return x + y - z; };
auto params = make_tuple(1,2,3);
auto res = fn_tuple_apply(f, params);
print(res);
// combine tuple using overloaded operators and apply function
auto list = make_tuple(1,4);
auto res2 = fn_tuple_apply(f, list << 4);
print(res2);
// ok, now i want piped templates
tfn_count count;
vector<string> slist = {"one", "two", "three"};
slist | count | print;
// piping....
auto ucount = fn_to_universal(count);
auto uprint = fn_to_universal(print);
slist | ucount | print;
// currying....
auto uf = fn_to_universal(f);
auto uf1 = uf << 1;
auto uf2 = uf1 << 2 << 5;
uf2() | print;
1 | (uf << 4 << 6) | print; // 4+6-1 = 9
3 | (uf >> 6 >> 7) | print; // 3+6-7 = 2
// count sum length of all strings in the list
slist | (fmap >> count) | (reduce >> 0 >> sum) | (uprint << "Total: " >> " chars");
slist | (reduce >> string("") >> sum) | (uprint << "All: ");
vector<int>{1,2,3} | (reduce >> 0 >> sum) | (uprint << "Sum: ");
// Some real objects...
vector<User> users {make<User>(1, "John", 0), make<User>(2, "Bob", 1), make<User>(3, "Max", 1)};
users | (filter >> (isName >> "Bob")) | ucount | uprint;
vector<int>{1,2,6} | (fmap >> (ffind << users << isId)) | (filter >> isNotNull) | ucount | uprint;
// Produce XML
users | fmap >> [](User u){ return u->name; } | fmap >> (xmlWrap << "name") | reduce >> string("") >> sum | xmlWrap << "users" | print;
// Use functional chain:
auto f1 = [](int x){ return x+3; };
auto f2 = [](int x){ return x*2; };
auto f3 = [](int x) { return (double)x / 2.0; };
auto f4 = [](double x) { std::stringstream ss; ss << x; return ss.str(); };
auto f5 = [](string s) { return "Result: " + s; };
auto testChain = fn_chain<>() | f1 | f2 | f3 | f4 | f5;
testChain(3) | print;
auto countUsers = fn_chain<>() | (fmap >> (ffind << users << isId)) | (filter >> isNotNull) | ucount;
vector<int>{1,2,6} | countUsers | (uprint << "count of users: ");
// Ok, pipe through monadic execution!
maybe<int>(2) | (ffind << users << isId) | [](User u){ return u->name; } | [](string s){ LOG << s << NL; return s; };
(maybe<int>(6) | (ffind << users << isId) | [](User u){ return u->name; }).getOr("Not found") | (uprint << "Found user: ");
just(vector<int>{1,2,6}) | countUsers | [&](int count){ count | (uprint << "Count: "); return count; };
(just(vector<int>{1,2,6}) | countUsers || -1) | (uprint << "Count:");
return 0;
}
@dgu123
Copy link

dgu123 commented Apr 19, 2015

woow !

@xschrodingerscat
Copy link

very useful!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment