簡體   English   中英

使用C ++中的參數包進行功能編程

[英]Functional programming using parameter packs in C++

這是我遇到的另一個問題的簡化,但是它本身就很好。 這樣做是為了實現類似功能的原語mapapply方案。

回顧一下:在Scheme中,給定一個函數f那么(apply f '(1 2 3))等效於(f 1 2 3)(map f '(1 2 3))等效於((f 1) (f 2) (f 3))

實施apply是一件容易的事,還有許多其他問題說明如何實現:

template <class Func, class... Args, std::size_t... Ixs>
auto apply_helper(Func&& func, const tuple<Args...>& args,
                  index_sequence<Ixs...>)
    -> decltype(func(get<Ixs>(args)...))
{
  return forward<Func>(func)(get<Ixs>(forward<const tuple<Args...>&>(args))...);
}

template <class Func, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto apply(Func&& func, const tuple<Args...>& args)
    -> decltype(apply_helper(func, args, Ixs()))
{
  return apply_helper(forward<Func>(func),
                      forward<const tuple<Args...>&>(args), Ixs());
}

void print3(int x, const char* s, float f) {
  cout << x << "," << s << "," << f << endl;
}

int main() {
  auto args = make_tuple(2, "Hello", 3.5);
  apply(print3, args);
}

現在來實現map ,這有點棘手。 我們希望這樣的事情起作用,所以這就是目標(在這里使用mapcar避免與std::map沖突):

template <class Type>
bool print1(Type&& obj) {
  cout << obj;
  return true;
}

int main() {
  auto args = make_tuple(2, "Hello", 3.5);
  mapcar(print1, args);
}

傳遞print1函數的其他替代方法也可以。

因此,如果我們對函數進行硬編碼,則以下代碼可以正常工作:

template <class... Args, std::size_t... Ixs>
auto mapcar_helper(const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(print1(get<Ixs>(args))...))
{
  return make_tuple(print1(get<Ixs>(forward<const tuple<Args...>&>(args)))...);
}

template <class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(const tuple<Args...>& args)
    -> decltype(mapcar_helper(args, Ixs()))
{
  return mapcar_helper(forward<const tuple<Args...>&>(args), Ixs());
}

問題是我們如何才能泛化此代碼以接受任意名稱作​​為輸入並使其解析模板中的名稱查找? 僅添加模板參數是行不通的,因為它當時無法解決函數重載。

我們希望對mapcar的調用與代碼等效:

make_tuple(print1(2), print1("Hello"), print1(3.5));

更新:最初的挑戰之一是使其與C ++ 11編譯器一起使用,部分原因是因為我正在使用GCC 4.8,而且還因為我想研究如何做到這一點。 基於這些注釋,下面是一個示例,說明了如何在無需多態lambda(需要C ++ 14編譯器支持)的幫助下完成此操作。

它並不像我希望的那樣簡單明了,C ++ 14的功能將使它變得如此簡單,但是至少可以為用戶帶來一點不便而支持它。

template <class Func, class... Args, std::size_t... Ixs>
auto mapcar_helper(Func&& func, const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(func(get<Ixs>(args))...))
{
  return make_tuple(func(get<Ixs>(args))...);
}

template <class Func, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(Func&& func, const tuple<Args...>& args)
   -> decltype(mapcar_helper(func, args, Ixs())
{
  return mapcar_helper(forward<Func>(func), forward<decltype(args)>(args), Ixs());
}

為了能夠傳遞模板“功能”,我們​​需要將其包裝在一個對象中:

struct print1 {
  template <class Type> const Type& operator()(Type&& obj) {
    std::cout << obj << " ";
    return obj;
  }
};

現在可以將其傳遞給函數,並在參數包擴展時完成類型查找:

   mapcar(print1(), make_tuple(2, "Hello", 3.5));
template <typename F, class... Args, std::size_t... Ixs>
auto mapcar_helper(F f, const tuple<Args...>& args,
                   index_sequence<Ixs...>)
    -> decltype(make_tuple(f(get<Ixs>(args))...))
{
  return make_tuple(f(get<Ixs>(args))...);
}

template <typename F, class... Args,
          class Ixs = make_index_sequence<sizeof...(Args)>>
auto mapcar(F f, const tuple<Args...>& args)
    -> decltype(mapcar_helper(move(f), args, Ixs()))
{
  return mapcar_helper(move(f), args, Ixs());
}

然后,您執行以下操作:

mapcar([](auto&& obj) { return print1(std::forward<decltype(obj)>(obj)); }, args);

也許我不明白這個問題。 您需要將print1包裝在一個lambda中,因為否則它會變得模棱兩可; 您想傳遞哪個print1實例?


如果您沒有恐懼症,可以使用宏使它更優雅:

#define LIFT(F) ([&](auto&&... args) -> decltype(auto) { \
    return F(::std::forward<decltype(args)>(args)...);  \
})

然后,您可以使用mapcar(LIFT(print1), args)


這就是我編寫自己的map函數的方式:

template<typename F, class Tuple, std::size_t... Is>
auto map(Tuple&& tuple, F f, std::index_sequence<Is...>)
{
    using std::get;
    return std::tuple<decltype(f(get<Is>(std::forward<Tuple>(tuple))))...>{
        f(get<Is>(std::forward<Tuple>(tuple)))...
    };
}

template<typename F, class Tuple>
auto map(Tuple&& tuple, F f)
{
    using tuple_type = std::remove_reference_t<Tuple>;
    std::make_index_sequence<std::tuple_size<tuple_type>::value> seq;
    return (map)(std::forward<Tuple>(tuple), std::move(f), seq);
}

我錯過了什么?

#include <iostream>
#include <string>


template<class F, class...Args>
void map(F&& f, Args&&...args)
{
    using expander = int[];
    (void) expander { 0, ((void) f(args), 0)... };
}

auto main() -> int
{
    using namespace std;

    map([](const auto& x) { cout << x << endl; }, 1, "hello"s, 4.3);

    return 0;
}

預期輸出:

1
hello
4.3

請注意,在c ++ 17中, map()函數變得更加令人愉悅:

template<class F, class...Args>
void map(F&& f, Args&&...args)
{
    (f(args), ...);
}

如果您的下一個問題是“為什么要用括號?”。 答案是因為折疊表達式僅在表達式的上下文中求值。 f(arg1), f(arg2); 是一個聲明。

參考: http : //en.cppreference.com/w/cpp/language/fold

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM