I am trying to come up with a monoid
type class in C++. Here I am giving a working example of what I have devised so far.
#include <utility>
#include <vector>
#include <iostream>
#include <string>
#include <type_traits>
#include <iterator>
#include <algorithm>
#include <list>
template<typename T, typename BinaryOp>
struct monoid
{
//T mempty();
//T mappend(T,T);
static constexpr bool is_instance = false;
using type = T;
};
template<>
struct monoid<int, std::plus<int>>
{
static int mempty() { return 0 ;}
static int mappend(int lhs, int rhs) {return lhs + rhs; }
static constexpr bool is_instance = true;
using type = int;
};
template<>
struct monoid<double, std::multiplies<double>>
{
static double mempty() { return 1.0 ;}
static double mappend(double lhs, double rhs) {return lhs * rhs; }
static constexpr bool is_instance = true;
using type = double;
};
template<>
struct monoid<int, std::multiplies<int>>
{
static int mempty() { return 1 ;}
static int mappend(int lhs, int rhs) {return lhs * rhs; }
static constexpr bool is_instance = true;
using type = int;
};
template <>
struct monoid<std::string, std::plus<std::string>> {
static std::string mempty(){return std::string{};}
static std::string mappend(std::string const& lhs,
std::string const& rhs){
return lhs + rhs;
}
static constexpr bool is_instance = true;
using type = std::string;
};
template <typename Container>
struct ContainerAppender{
Container operator()(Container const& lhs, Container const& rhs)
{
Container dst;
std::merge(std::begin(lhs), std::end(lhs),
std::begin(rhs), std::end(rhs),
std::back_inserter(dst));
return dst;
}
};
template <typename Container>
struct monoid<Container, ContainerAppender<Container>> {
static Container mempty(){ return Container{}; }
static Container mappend(Container const& lhs,
Container const& rhs){
return ContainerAppender<Container>{}(lhs, rhs);
}
static constexpr bool is_instance = true;
using type = Container;
};
template <typename T, typename BinaryOp>
constexpr bool is_monoid_v = monoid<T, BinaryOp>::is_instance;
template <typename T, typename BinaryOp>
using is_monoid = std::enable_if_t<is_monoid_v<T, BinaryOp>>;
template<template <typename> typename BinaryOp,
typename ForwardIt,
typename = is_monoid<typename ForwardIt::value_type,
BinaryOp<typename ForwardIt::value_type>>>
auto mconcat(const ForwardIt begin, const ForwardIt end)
{
using T = typename ForwardIt::value_type;
T out{monoid<T, BinaryOp<T>>::mempty()};
for(ForwardIt it = begin; it != end; it++){
out = monoid<T, BinaryOp<T>>::mappend(out,*it);
}
return out;
}
int main(){
std::vector<std::string>str_vec{"1","2","3"};
std::cout << mconcat<std::plus>(str_vec.begin(),
str_vec.end())
<< "\n";
std::list<double> dbl_li{1.0, 2.3, 4.5};
double acc = mconcat<std::multiplies>(std::begin(dbl_li),
std::end(dbl_li));
std::cout << acc << "\n";
// Reduce vectors as well, nested reduction
std::vector<std::vector<int>> nested_vec{{1,2,3},{4,5,6},{7,8,9}};
const auto v= mconcat<ContainerAppender>(std::begin(nested_vec),
std::end(nested_vec));
int res = mconcat<std::multiplies>(std::begin(v),
std::end(v));
std::cout << res << "\n";
return 0;
}
- What are general code smells you can identify?
- Are there any problems with my usage of templates and generic programming, how that can be improved?
- Can you suggest improvements on usability and extendibility of
monoid
class?