#include <iostream>
#include <algorithm>
#include <type_traits>
#include <vector>
#include <set>
#include <map>
#include <array>


template <class InputContainer, class OutputType>
struct convert_container;

// conversion for the first group of standard containers
template <template <class...> class C, class IT, class OT>
struct convert_container<C<IT>, OT>
{
    using type = C<OT>;
};

// conversion for the second group of standard containers
template <template <class...> class C, class IK, class IT, class OK, class OT>
struct convert_container<C<IK, IT>, std::pair<OK, OT>>
{
    using type = C<OK, OT>;
};

// conversion for the third group of standard containers
template
    <
        template <class, std::size_t> class C, std::size_t N, class IT, class OT
    >
struct convert_container<C<IT, N>, OT>
{
    using type = C<OT, N>;
};

template <typename C, typename T>
using convert_container_t = typename convert_container<C, T>::type;

template
    <
        class InputContainer,
        class Functor,
        class InputType = typename InputContainer::value_type,
        class OutputType = typename std::result_of<Functor(InputType)>::type,
        class OutputContainer = convert_container_t<InputContainer, OutputType>
    >
OutputContainer transform_container(const InputContainer& ic, Functor f)
{
    OutputContainer oc;

    std::transform(std::begin(ic), std::end(ic), std::inserter(oc, oc.end()), f);

    return oc;
}

int main()
{
    std::cout << std::boolalpha;
    
    std::vector<int> vi{ 1, 2, 3, 4, 5 };
    auto vs = transform_container(vi, [] (int i) { return std::to_string(i); }); 
    std::cout << "std::vector<int> -> std::vector<std::string>: "
        << (vs == std::vector<std::string>{"1", "2", "3", "4", "5"}) << std::endl;
        
    std::set<int> si{ 5, 10, 15 };
    auto sd = transform_container(si, [] (int i) { return i / 2.; }); 
    std::cout << "std::set<int> -> std::set<double>: "
        << (sd == std::set<double>{5/2., 10/2., 15/2.}) << std::endl;
    
    std::map<int, char> mic{ {1, 'a'}, {2, 'b'}, {3, 'c'} };
    auto mci = transform_container
        (
            mic,
            [] (const std::pair<const int, char>& p)
            { return std::pair<char, int>(p.second, p.first); }
        ); 
    std::cout << "std::map<int, char> -> std::map<char, int>: "
        << (mci == std::map<char, int>{{'a', 1}, {'b', 2}, {'c', 3}}) << std::endl;
    
    // doesn't compile because std::array haven't insert method
    // which is needed due to std::inserter
    /*std::array<int, 3> ai{ 5, 10, 15 };
    auto ad = transform_container(ai, [] (int i) { return i / 2.; }); 
    std::cout << "std::array<int, 3> -> std::array<double, 3>: "
        << (ad == std::array<double, 3>{5/2., 10/2., 15/2.}) << std::endl;*/
}