6

どちらも、単なる基本的なものよりも高度なテンプレート構造の実装について学び、多くの状況で役立つため、decltypeなどのc ++ 11構造を使用して、関数型プログラミングで一般的なマップ、フィルター、および同様の関数を実装しようとしています。

使用しているコンパイラが処理できる関数プロトタイプの作成に問題があるため、次のようなものを作成する方法を尋ねる必要があります。

//
// Takes an iterable, applies a function to every element, and returns a vector of the results
//
template <typename T, typename Func>
auto map(const T& iterable, Func func) -> std::vector< decltype(  func( *iterable.cbegin() ) ) >
{
    // body snipped
}

つまり、この関数は、任意のiterableと、iterables値型を引数として取り、ある種の値を返す関数を受け取る必要があります。関数呼び出しの結果は、渡されたiterableのタイプに関係なく、渡された関数が返すタイプのベクトルになります。

map関数は、関数ポインター、ファンクター、ラムダ式のいずれであっても、有効なプロトタイプを引数として持つすべての関数を受け入れる必要があります。

このテストコードで上記の関数を使用する:

std::vector<int> intVector;
intVector.push_back(1);
intVector.push_back(2);

map(intVector, [](int& value) { return value + 1; });

Visual StudioがC2893( "関数テンプレートの特殊化に失敗しました")エラーを吐き出しますが、何が問題なのかわかりません。

更新: これまでの質問に対するコメントと回答で提案された変更を適用し、新しいプロトタイプをテストしましたが、同じエラーが残っています。

4

3 に答える 3

8

これはあなたが望むことをするかもしれません。std::transform内部で使用され、基本的にすべての作業を行います。私が書いた関数は、コンテナーの単純なラッパーにすぎません (C スタイルの配列では機能しないため、追加の型特性が必要になります)。

#include <vector>
#include <algorithm>
#include <type_traits>

//
// Takes an iterable, applies a function to every element, 
// and returns a vector of the results
//
template <typename T, typename Func>
auto map_container(const T& iterable, Func&& func) ->
    std::vector<decltype(func(std::declval<typename T::value_type>()))>
{
    // Some convenience type definitions
    typedef decltype(func(std::declval<typename T::value_type>())) value_type;
    typedef std::vector<value_type> result_type;

    // Prepares an output vector of the appropriate size
    result_type res(iterable.size());

    // Let std::transform apply `func` to all elements
    // (use perfect forwarding for the function object)
    std::transform(
        begin(iterable), end(iterable), res.begin(),
        std::forward<Func>(func)
        );

    return res;
}

constただし、ラムダは への参照を取る必要があることに注意してくださいint

また、関数の名前を から に変更しました。C++ 標準ライブラリの標準コンテナーの名前を、プログラム内の関数、変数、またはその他のものに再利用mapするmap_containerことは、プログラミングの悪い習慣です。

私にとっては、これにより目的の出力が得られます。

#include <iostream>

int main()
{
    std::vector<int> intVector;

    intVector.push_back(1);
    intVector.push_back(2);

    auto v = map_container(intVector, [] (int value) { return value + 1; });

    for (int i : v) { std::cout << i << " "; }
}
于 2013-02-18T21:17:43.277 に答える
4

したがって、ここで処理するコーナーケースがたくさんあります。私がやろうとしていることは、最初にいくつかのcontainer_traitsテンプレートを作成して、可能な限り多くの作業を抽象化することです。

とfree 関数containerの呼び出しを許可し、 と が を介して実行され、これら 2 つの型が同じである場合、型はaです(最後は要件ではない可能性があります)。beginendstd::beginstd::endusing

a の特性は、containerほとんどの場合iterator、コンテナーが持つ s に加えて、前述のイテレーターの型から派生します。その他のいくつかの機能size(またはsize_at_least以下を参照) は一般的です。

iterable型の が である場合、const型は であると言われますcontainer

次の質問は、「コンテナの要素をマッピングするのに有効な型インスタンスの種類は何ですか?」です。-- これも少し重要なので、それに対処するためにいくつかの特性クラスを追加しました。

したがって、これは次の実装につながります。

#include <algorithm>
#include <type_traits>
#include <utility>

namespace aux {
  // calculate the type that calling `begin` and `end` on a type will return
  // in a scope where `std::begin` and `std::end` are visible.  This hack is
  // required to enable argument-dependent lookup.
  using std::begin;
  using std::end;
  template<typename T>
  auto adl_begin(T&&t)->decltype( begin(std::forward<T>(t)) );
  template<typename T>
  auto adl_end(T&&t)->decltype( end(std::forward<T>(t)) );
  template<typename T>
  auto adl_cbegin(T const&t)->decltype( begin(t) );
  template<typename T>
  auto adl_cend(T const&t)->decltype( end(t) );
}

// What is a container?  Something with a `begin`ing and an `end`ing...
template<typename C,typename=void>
struct is_container:std::false_type {};
template<typename C>
struct is_container<C, typename std::enable_if<
   std::is_same<
      decltype(aux::adl_begin(std::declval<C>())),
      decltype(aux::adl_end(std::declval<C>()))
   >::value
>::type >:std::true_type {};


// Default container_traits is empty for SFINAE ease of use:
template<typename C, typename=void>
struct container_traits {};

// if it is a container, go in whole hog:
template<typename C>
struct container_traits<C, typename std::enable_if< is_container<C>::value >::type >
{
   typedef decltype( aux::adl_begin(std::declval<C>()) ) iterator;
   typedef decltype( aux::adl_cbegin(std::declval<C>()) ) const_iterator;
   // I'm lazy, so I'll copy typedefs from `iterator_traits` below:
   typedef typename std::iterator_traits<iterator>::value_type value_type;
   typedef typename std::iterator_traits<iterator>::reference reference;
   // etc

   // TODO: size_at_least is a helper function
   // it returns 0 if it is expensive to calculate the size (say, a range
   // if iterators into a `std::list`), and the size if it is cheap to
   // calculate (say, a `std::vector`, any class with a `.size()` method,
   // or a pair of pointers or other random-access iterators)
   // template<typename C2, typename=typename std::enable_if< std::is_convertable< C2, C const&>::value>::type
   // static std::size_t size_at_least( C2&& c ) { ... }
};

// Can Functor map the elements of C into something we can store elsewhere?
template<typename C, typename Functor, typename=void>
struct can_map:std::false_type {};
// Yes, if the result of calling Functor on C's elements is non-void:
template<typename C, typename Functor>
struct can_map<C, Functor, typename std::enable_if<
  !std::is_same< decltype(std::declval<Functor>()(std::declval<typename container_traits<C>::value_type>())), void >::value
>::type>: std::true_type {};

// The result of mapping the elements of C under Functor
template<typename C, typename Functor, typename=void>
struct map_result {};
template<typename C, typename Functor>
struct map_result<C,Functor,typename std::enable_if< can_map<C,Functor>::value>::type>
{
  typedef
    decltype(
      std::declval<Functor>()(
        *std::declval<
          typename container_traits<C>::const_iterator
        >()
      )
    )
  type;
};

// The actual implementation
// we std::decay the map_result because we want to store
// instances of the type, and std::decay does that quite nicely
// note that some pathological Functors may break this, ie ones
// that return pseudo-references that are intended to be read from
// yet are not std-container safe
template <typename T, typename Func>
auto map_container(T&& iterable, Func&& func) ->
  std::vector<
    typename std::decay<
      typename map_result<T, Func>::type
    >::type
  >
{
  std::vector<
    typename std::decay<
      typename map_result<T, Func>::type
    >::type
  > retval;
  // TODO: use container_traits<T>::size_at_least to reserve space in retval
  // that will bring the efficiency of this function up to near-hand-crafted-C.
  for (auto&& s:iterable) {
    retval.push_back( func(s) );
  }
  return retval;
}

それだけです。次に、コードをテストします。map_containerC スタイルの配列、vector従来の型 とbool(疑似参照を使用してビットを密にパックする) の s、および.begin()メソッドと自由浮動begin(C)関数の両方を介したユーザー定義の型でできるはずです。

配列に関して私が抱えていた問題の 1 つはC const&、配列内でポインターの減衰が発生しているように見えたため、配列がコンテナーではなくなったように見えたことC&&です。実際の配列型を取得するには、バインドする必要がありました。

#include <iostream>

void test1() {
   std::vector<int> src{1,2,3,4,5};
   auto r = map_container( src, [](int x){return x*2;});
   for (auto&& x:r) {
      std::cout << x << "\n";
   }
}
struct test_buffer {
  int foo[5];
  int* begin() { return foo; }
  int* end() { return &foo[5]; }
  int const* begin() const { return foo; }
  int const* end() const { return &foo[5]; }
};
test_buffer buff1={{1,2,3,4,5}};
struct test_buffer_2 {
  int foo[5];
};
test_buffer_2 buff2={{1,2,3,4,5}};
int* begin(test_buffer_2& t) { return t.foo; }
int* end(test_buffer_2& t) { return &t.foo[5]; }
int const* begin(test_buffer_2 const& t) { return t.foo; }
int const* end(test_buffer_2 const& t) { return &t.foo[5]; }
std::vector<bool> bits{true, false, true, false};   

template<typename Container>
void tester(Container&& c) {
   Container const& src = c;
   auto r = map_container( src, [](int x){return x*2;});
   for (auto&& x:r) {
      std::cout << x << "\n";
   }
}
void test2() {
   tester(buff1);
   tester(buff2);
   tester(bits);
}
template<typename C>
bool is_container_test(C&&) {
   return is_container<C>::value;
}
template<typename C, typename F>
bool can_map_test( C&&, F&& ) {
   return can_map<C, F>::value;
}
template<typename C, typename F>
bool can_map_test2( C const&, F&& ) {
   return can_map<C, F>::value;
}
int array[] = {1,2,3,4,5};
void test3() {
   std::cout << "Array is container:" << is_container_test(array) << "\n";
   auto x2 = [](int x){return x*2;};
   std::cout << "Double can map:" << can_map_test(array, x2) << "\n";
   std::cout << "Double can map:" << can_map_test2(array, x2) << "\n";
}
void test4() {
   tester(array);
}
int main() {
   test1();
   test2();
   test3();
   test4();
}

またはそれらの線に沿った何か。関数自体で複雑な SFINAE を実行しないでください。代わりに、作業を行う特性クラスを作成してください。

上記で使用したその他の手法:開始/終了イテレータを取得するためにstd::beginandを使用しました。std::endこれは、生の C 配列をサポートするようになったことを意味します。次に、これをいくつかの引数依存のルックアップ ヘルパーでラップしました。その目的は、同じ名前空間でクラス オーバーライドを定義できるようにすることbeginですend

の「受け入れなし」バージョンはcontainer_traits、未定義のものではなく、空の構造体であることに注意してください。これによりcontainer_traits、他の場所で SFINAE を使用できます。

ああ、効率の改善は、reserveメソッドを持つコンテナとコピーしたいサイズのコンテナを取る「スマートリザーブ」を書くことです。コピーするコンテナーにランダム アクセス イテレーターと.size()メソッドがない場合は何もしませんが、ある場合は.reserve( end(...)-begin(...) )orを実行し.reserve(src.size())ます。container_traitsこれをasに追加することで、他のアルゴリズムでこれを抽象化できstatic size_t size_at_least(Container const&)ます。size_tContainer

于 2013-02-18T21:24:31.760 に答える