On an std::list<A>
or std::vector<A>
or other collection container, the functor map (fmap) operation takes a function/callable of type B(A)
and applies it elementwise to a list of A
s to get a list of B
s:
// Pseudo-code
container<B> fmap( B(A) f, container<A> xs)
{
// Apply f elementwise to xs, and collect the results and...
return result;
}
If A
and B
are the same, then fmap
can take the list by copy, and mutate its copy and return it. Otherwise, I want to take the input container by const reference, and create a new container for the results. The following code works (using clang++4.0 -std=c++1z
) but it's just so unspeakably ugly for such a simple thing.
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <numeric>
#include <type_traits>
#include <vector>
// clang-format off
template <template <typename, typename> class Container, typename A, typename F,
// Enables this overload if the container's element type matches the output
// type of the callable:
typename = std::enable_if_t<std::is_same< A,
decltype(std::invoke(std::declval<F>(), std::declval<A>()))>::value>>
auto fmap(F f, Container<A, std::allocator<A>> xs) {
std::cout << "I/O type SYMMETRY DETECTED!!\n";
std::transform(std::begin(xs), std::end(xs), std::begin(xs), f);
return xs;
}
// clang-format on
// clang-format off
template <template <typename, typename> class Container, typename A, typename F,
typename = std::enable_if_t< ! std::is_same< A,
decltype(std::invoke(std::declval<F>(), std::declval<A>()))>::value>>
auto fmap(F f, const Container<A, std::allocator<A>>& xs) {
std::cout << "I/O type MISMATCH DETECTED!!\n";
using B = decltype(f(std::declval<A>()));
using ContainerOfB = Container<B, std::allocator<B>>;
ContainerOfB result;
if constexpr(std::is_same<ContainerOfB, std::vector<B>>::value) {
result.reserve(xs.size());
}
std::transform(std::begin(xs), std::end(xs), std::back_inserter(result), f);
return result;
}
template <typename T>
void printrange(const T r) {
std::copy(std::cbegin(r), std::cend(r),
std::ostream_iterator<decltype(*std::begin(r))>(std::cout, ", "));
}
int main() {
std::vector<int> v(20);
std::iota(v.begin(), v.end(), 0);
// clang-format off
printrange(
fmap([](int i) { return '\'' + std::to_string(i) + '\''; },
fmap([](int x) { return x * x; },
fmap([](int x) { return x + 1; }, v) ))
);
// clang-format on
return 0;
}
Forgive me, I'm new to to template metaprogramming, and I'm aware that I'm not doing anything difficult. In fact, everything I'm using is in <type_traits>
, so I'm not really doing any metafunction programming, I'm just using metafunctions! But the code I just wrote is so heinously ugly that I'm sure it can be polished and prettied. I seek the greater wisdom of the community to help me clean up this mess.