5

私は Haskell から来て、現在 C++11 で何ができるかを調べています。map私のおもちゃの 1 つは、Haskell関数を模倣しようとする小さなテンプレートです。つまり、 の値のコンテナと を にXマッピングする関数Xを取り、 の値Yのコンテナを生成しますY。を使用して簡単にできることはわかっていますstd::transformが、それでは楽しみが台無しになります。

現在、私のテンプレートは次のようになっています。

template <typename T, typename U>
void myMap( const T &input,
            U &output,
            std::function<typename U::value_type (typename T::value_type)> f );

さて、私の質問は次のとおりです。出力コンテナを参照(2番目の引数)で取得する代わりに、戻り値を介して新しいコンテナを生成し、コンパイラが戻り値の型を推測できるように署名を調整することは可能ですか? 何かのようなもの

template <typename T, typename U>
U myMap( const T &input,
       std::function<typename U::value_type (typename T::value_type)> f );

残念ながら、次のように呼び出すことはできません

std::vector<int> x = { 1, 2, 3, 4 };
std::list<bool> y = myMap( x, []( int x ) { return x % 2 == 0; } );

...少なくとも、Clang はここで戻り値の型を推測できません。

私が持っていた 1 つのアイデアは、入力コンテナーの型と関数の型がわかっている場合、そこから出力の型を構築できるというものでした。つまり、次のようなもの

template <typename C, typename T, typename U>
C<U> myMap( const C<T> &input,
            std::function<U (T)> f );

...しかし、悲しいかなC<U>、有効な構文ではないようです。この質問decltypeの場合のように、適切な妖精の粉が必要なだけなのだろうか。

4

4 に答える 4

8

前に述べたように、私は以前にこれをすべて行ったことがありますが、基礎となるコンテナではなく再バインドするだけなので、 std::basic_string<T,U>(およびstd::setの使用による友人) では機能しません。ただし、特殊なケースで動作するように拡張するのは簡単であることに注意してください。std::back_inserterstd::basic_string<stuff,U>std::basic_string<T, U>

私が最初にしたことは、渡される関数の結果の型であり、元の型であるから型を再バインドfunction_traitsするメタ関数を定義することでした。結果の型は、メタ関数を介して検出されます。以下の完全に機能するコードを確認できます。RebindContainer<T>Container<U>UTfunction_traits

#include <type_traits>
#include <algorithm>

/* Helpers */
template<typename T>
using Type = typename T::type;

template<typename T>
using Unqualified = Type<std::remove_reference<Type<std::remove_cv<T>>>>;

template<typename Specialization, typename Target>
struct rebind {};

/* Sensible default: assume first parameter is for the target */
template<template<typename...> class Cont, typename T, typename... Ts, typename Target>
struct rebind<Cont<T, Ts...>, Target> {
    using type = Cont<Target, Ts...>;
};

/* Special-case */
template<typename Old, std::size_t N, typename Target>
struct rebind<std::array<Old, N>, Target> {
    using type = std::array<Target, N>;
};

template<typename Specialization, typename Target>
using Rebind = Type<rebind<Specialization, Target>>;

#include <tuple>

template<typename T>
struct function_traits : public function_traits<decltype(&T::operator())> {};

template<typename T, typename R, typename... Args>
struct function_traits<R(T::*)(Args...) const> {

    static constexpr size_t args = sizeof...(Args);

    using result_type = R;
    template<size_t i>
    struct arg {
        using type = typename std::tuple_element<i,std::tuple<Args...>>::type;
    };
};

template<typename T>
using Resultant = typename function_traits<T>::result_type;

template<class Cont, typename Map>
auto map(const Cont& cont, Map&& mapped) -> Rebind<Cont, Resultant<Unqualified<Map>>> {
    Rebind<Cont, Resultant<Unqualified<Map>>> result;
    auto result_iterator = std::back_inserter(result);
    for(const auto& elem : cont) {
        *result_iterator = mapped(elem);
    }
    return result;
}

#include <iostream>

int main() {
    auto i = map(std::vector<int>{1,2,3,4,5,6}, [](int x) { return x % 2 == 0; });
    for(auto&& j : i) {
        std::cout << j << ' ';
    }
}

出力:

0 1 0 1 0 1

Coliruのライブバージョン

于 2013-07-19T20:37:24.573 に答える
4

あなたはおそらくこの構文を探しています

#include <algorithm>
#include <functional>
#include <type_traits>
#include <list>

template 
    <
       template<typename, typename...> class Container, 
       typename InType, 
       typename FuncType, 
       typename... Rest
    >
auto myMap (const Container<InType, Rest...>& container,
            FuncType func) -> 
              Container<decltype(func(std::declval<InType>())), Rest...>
{
    Container<decltype(func(std::declval<InType>())), Rest...> result;
    std::transform(std::begin(container), 
                   std::end(container),
                   std::back_inserter(result), 
                   func);
    return result;
}

ただし、実際のプロジェクトでこのスタイルのコードを使用することはお勧めしません。

于 2013-07-19T21:00:32.503 に答える
0

nmの答えは、私が正しい答えと見なすものですが、ネストされたテンプレートの使いやすさへの影響は、正しさの価値があるとは思いません.

これが私の試みです:

template <typename T, typename F, typename C = 
        std::vector<typename std::result_of<F(typename T::value_type)>::type>>
C myMap(const T &input, F f) {
    C ret;
    for (const auto& x : input) {
        ret.push_back(f(x));
    }
    return ret;
}

これは、元のコードとの 2 つの違いを示しています。1 つは、std::function を指定していませんでした。そのクラスはほとんど必要ないオーバーヘッドを導入するためです。2 つ目は、std::list の代わりに std::vector をデフォルトの戻り値の型として指定したことです (この例ではリストを使用しました)。vector は推奨される C++ コンテナです。ベンチマークに裏打ちされた別のコンテナを使用する圧倒的に強い理由がない限り、これを使用してください。

上記の私のアプローチの弱点の 1 つは、テンプレートへの前の引数である推定型をリストすることが困難または不可能な場合があることです。私の意見では、これを解決する最も簡単な方法はヒント引数です。

template <class T> struct hint {};

template <typename T, typename F, typename C>
C myMap(const T& input, F f, hint<C> h) {
    return myMap<T, F, C>(input, f);
}

もちろん、この時点で、出力コンテナーを入力として使用することもできます。

于 2013-07-19T21:37:21.410 に答える