Eşleşen işlev işaretçisini çağırmak için bir parçayı “açmak”


255

Daha std::tuplesonra depolanan türleri eşleşen bir işlev işaretçisi için bir çağrı için bağımsız değişken olarak kullanılacak değerleri, değişen sayıda depolamaya çalışıyorum .

Çözmek için mücadele ettiğim sorunu gösteren basitleştirilmiş bir örnek oluşturdum:

#include <iostream>
#include <tuple>

void f(int a, double b, void* c) {
  std::cout << a << ":" << b << ":" << c << std::endl;
}

template <typename ...Args>
struct save_it_for_later {
  std::tuple<Args...> params;
  void (*func)(Args...);

  void delayed_dispatch() {
     // How can I "unpack" params to call func?
     func(std::get<0>(params), std::get<1>(params), std::get<2>(params));
     // But I *really* don't want to write 20 versions of dispatch so I'd rather 
     // write something like:
     func(params...); // Not legal
  }
};

int main() {
  int a=666;
  double b = -1.234;
  void *c = NULL;

  save_it_for_later<int,double,void*> saved = {
                                 std::tuple<int,double,void*>(a,b,c), f};
  saved.delayed_dispatch();
}

Normalde std::tupleveya değişken şablonlar içeren sorunlar için, template <typename Head, typename ...Tail>tüm türleri teker teker tekrarlı olarak değerlendirmek gibi başka bir şablon yazardım , ancak bir işlev çağrısı göndermek için bunu yapmanın bir yolunu göremiyorum.

Bunun için gerçek motivasyon biraz daha karmaşıktır ve çoğunlukla sadece bir öğrenme alıştırmasıdır. Tupu başka bir arayüzden sözleşmeyle teslim ettiğimi varsayabilirsin, bu yüzden değiştirilemez, ancak bir fonksiyon çağrısına açma arzusu benimdir. Bu std::bind, altta yatan sorunu ortadan kaldırmak için ucuz bir yol olarak kullanılmasını önler.

Aramayı kullanarak aramanın gönderilmesinin temiz bir yolu std::tupleveya bazı değerleri ve bir işlev işaretçisini keyfi bir gelecekteki noktaya kadar saklamanın / iletmenin aynı net sonucunu elde etmenin alternatif daha iyi bir yolu nedir?


5
Neden sadece kullanmýyorsun auto saved = std::bind(f, a, b, c);... o zaman sonra araman yeterli saved()?
Charles Salvia

Her zaman benim arayüzü kontrol etmek değil. Birinden sözleşme ile bir demet alırım ve daha sonra onunla bir şeyler yapmak istiyorum.
Flekso

Yanıtlar:


62

C ++ 17 çözümü basitçe std::apply:

auto f = [](int a, double b, std::string c) { std::cout<<a<<" "<<b<<" "<<c<< std::endl; };
auto params = std::make_tuple(1,2.0,"Hello");
std::apply(f, params);

Sadece bu konudaki bir cevapta bir kez belirtilmesi gerektiğini hissettim (yorumlardan birinde zaten göründükten sonra).


Bu iş parçacığında temel C ++ 14 çözümü hala eksik. EDIT: Hayır, aslında Walter'ın cevabında.

Bu işlev verilir:

void f(int a, double b, void* c)
{
      std::cout << a << ":" << b << ":" << c << std::endl;
}

Aşağıdaki kod parçacığıyla arayın:

template<typename Function, typename Tuple, size_t ... I>
auto call(Function f, Tuple t, std::index_sequence<I ...>)
{
     return f(std::get<I>(t) ...);
}

template<typename Function, typename Tuple>
auto call(Function f, Tuple t)
{
    static constexpr auto size = std::tuple_size<Tuple>::value;
    return call(f, t, std::make_index_sequence<size>{});
}

Misal:

int main()
{
    std::tuple<int, double, int*> t;
    //or std::array<int, 3> t;
    //or std::pair<int, double> t;
    call(f, t);    
}

DEMO


Bu demonun akıllı işaretçilerle çalışmasını sağlayamıyorum - burada yanlış olan ne? http://coliru.stacked-crooked.com/a/8ea8bcc878efc3cb
Xeverous

@Xeverous: Burada böyle bir şey almak ister misiniz ?
davidhigh

teşekkürler, 2 sorum var: 1. Neden std::make_uniquedoğrudan geçemiyorum ? Somut işlev örneğine ihtiyacı var mı? 2. Neden std::move(ts)...biz değiştirebilirsem [](auto... ts)için [](auto&&... ts)?
Xeverous

@Xeverous: 1. imzalardan çalışmaz: std::make_uniquebir tuple bekler ve bir tuple sadece açılmış bir tuple sadece başka bir çağrı ile oluşturulabilir std::make_tuple. Bu lambda'da yaptığım şey (her ne kadar son derece fazla olsa da, herhangi bir kullanım olmadan tuplei benzersiz işaretçiye de kopyalayabilirsiniz call).
davidhigh

1
Şimdi bu cevap olmalı .
Fureeish

275

Bir parametre paketi numarası oluşturmanız ve bunları paketinden çıkarmanız gerekir

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};


// ...
  void delayed_dispatch() {
     callFunc(typename gens<sizeof...(Args)>::type());
  }

  template<int ...S>
  void callFunc(seq<S...>) {
     func(std::get<S>(params) ...);
  }
// ...

4
Vay canına, açma operatörünün böyle kullanılabileceğini bilmiyordum, bu güzel!
Luc Touraille

5
Johannes, bunu yayınladığından beri 2 yıldan fazla olduğunu fark ettim, ama mücadele ettiğim tek şey struct gensgenel tanım ( aynı genişletilmiş bir türetmeden miras kalan ). Sonunda 0 ile uzmanlığa isabet ettiğini görüyorum. Eğer ruh hali size uygunsa ve yedek döngüler varsa, bunu genişletebilirseniz ve bunun için nasıl kullanılırsa, sonsuza dek minettar olurum. Ve keşke bunu yüz kere oylayabilseydim. Bu koddan teğetlerle oynarken daha çok eğlendim. Teşekkürler.
WhozCraig

22
@WhozCraig: Yaptığı şey bir tür üretmektir seq<0, 1, .., N-1>. Nasıl çalışır: gens<5>: gens<4, 4>: gens<3, 3, 4>: gens<2, 2, 3, 4> : gens<1, 1, 2, 3, 4> : gens<0, 0, 1, 2, 3, 4>. Son tip uzmanlaşmıştır seq<0, 1, 2, 3, 4>. Oldukça akıllı bir numara.
mindvirüs

2
@NirFriedman: Tabii, sadece uzman olmayan sürümünü değiştirin gens:template <int N, int... S> struct gens { typedef typename gens<N-1, N-1, S...>::type type; };
marton78

11
Walter'ın cevabını ve yorumlarını tekrarlamaya değer: halk artık kendi tekerleklerini icat etmeye gerek yok. Bir sekans oluşturmak o kadar yaygındı ki C ++ 14'te olduğu gibi standardize edildi std::integer_sequence<T, N>ve bunların uzmanlığı std::size_t, std::index_sequence<N>artı bunların yardımcı fonksiyonları std::make_in(teger|dex)_sequence<>()ve std::index_sequence_for<Ts...>(). Ve C ++ 17'de kütüphaneye entegre edilmiş çok sayıda başka iyi şey var - özellikle de std::applyve std::make_from_tupleaçma ve çağıran bitleri işleyecek - ve dahil
underscore_d

44

Bu, Johannes'in awoodland'ın sorununa çözümünün tamamen derlenebilir bir versiyonudur , umarım birisi için yararlı olabilir. Bu, Debian sıkımında g ++ 4.7'nin anlık görüntüsü ile test edildi.

###################
johannes.cc
###################
#include <tuple>
#include <iostream>
using std::cout;
using std::endl;

template<int ...> struct seq {};

template<int N, int ...S> struct gens : gens<N-1, N-1, S...> {};

template<int ...S> struct gens<0, S...>{ typedef seq<S...> type; };

double foo(int x, float y, double z)
{
  return x + y + z;
}

template <typename ...Args>
struct save_it_for_later
{
  std::tuple<Args...> params;
  double (*func)(Args...);

  double delayed_dispatch()
  {
    return callFunc(typename gens<sizeof...(Args)>::type());
  }

  template<int ...S>
  double callFunc(seq<S...>)
  {
    return func(std::get<S>(params) ...);
  }
};

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
int main(void)
{
  gens<10> g;
  gens<10>::type s;
  std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
  save_it_for_later<int,float, double> saved = {t, foo};
  cout << saved.delayed_dispatch() << endl;
}
#pragma GCC diagnostic pop

Aşağıdaki SConstruct dosyası kullanılabilir

#####################
SConstruct
#####################
#!/usr/bin/python

env = Environment(CXX="g++-4.7", CXXFLAGS="-Wall -Werror -g -O3 -std=c++11")
env.Program(target="johannes", source=["johannes.cc"])

Makinemde bu

g++-4.7 -o johannes.o -c -Wall -Werror -g -O3 -std=c++11 johannes.cc
g++-4.7 -o johannes johannes.o

Neden s ve g değişkenlerine ihtiyacınız var?
shoosh

Sanırım onlara ihtiyaç yok. Bunları neden eklediğimi unutuyorum; neredeyse üç yıl oldu. Ama sanırım, örneklemenin işe yaradığını göstermek için.
Faheem Mitha

42

İşte bir C ++ 14 çözümü.

template <typename ...Args>
struct save_it_for_later
{
  std::tuple<Args...> params;
  void (*func)(Args...);

  template<std::size_t ...I>
  void call_func(std::index_sequence<I...>)
  { func(std::get<I>(params)...); }
  void delayed_dispatch()
  { call_func(std::index_sequence_for<Args...>{}); }
};

Bu hala bir yardımcı fonksiyona ( call_func) ihtiyaç duyar . Bu ortak bir deyim olduğu için, belki de standart std::callolası uygulamada olduğu gibi doğrudan desteklemelidir

// helper class
template<typename R, template<typename...> class Params, typename... Args, std::size_t... I>
R call_helper(std::function<R(Args...)> const&func, Params<Args...> const&params, std::index_sequence<I...>)
{ return func(std::get<I>(params)...); }

// "return func(params...)"
template<typename R, template<typename...> class Params, typename... Args>
R call(std::function<R(Args...)> const&func, Params<Args...> const&params)
{ return call_helper(func,params,std::index_sequence_for<Args...>{}); }

Sonra gecikmeli gönderimiz olur

template <typename ...Args>
struct save_it_for_later
{
  std::tuple<Args...> params;
  std::function<void(Args...)> func;
  void delayed_dispatch()
  { std::call(func,params); }
};

8
İçin (önerilen) uygulanması onaylandı std::call. C ++ 14'ün kaotik hayvanat bahçesi integer_sequenceve index_sequenceyardımcı türleri burada açıklanmaktadır: en.cppreference.com/w/cpp/utility/integer_sequence Göze çarpan yokluğuna dikkat edin std::make_index_sequence(Args...), bu yüzden Walter daha karmaşık sözdizimine zorlandı std::index_sequence_for<Args...>{}.
Quuxplusone

3
Ve görünüşe göre 3/2016'dan bu yana C ++ 17'ye std :: Apply (func, tup) olarak oy verdi: en.cppreference.com/w/cpp/utility/apply
ddevienne

18

Bunu başarmak biraz karmaşıktır (mümkün olmasına rağmen). Bunun zaten uygulandığı bir kütüphane kullanmanızı öneririm, yani Boost.Fusion ( invoke işlevi). Bonus olarak, Boost Fusion C ++ 03 derleyicileriyle de çalışır.


7

çözüm. İlk olarak, bazı yardımcı kazanlar:

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
  return [](auto&&f)->decltype(auto){
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}){
  return index_over( std::make_index_sequence<N>{} );
}

Bunlar, derleme zamanı tamsayılarına sahip bir lambda çağırmanıza izin verir.

void delayed_dispatch() {
  auto indexer = index_upto<sizeof...(Args)>();
  indexer([&](auto...Is){
    func(std::get<Is>(params)...);
  });
}

ve işimiz bitti.

index_uptove index_overyeni bir harici aşırı yük oluşturmak zorunda kalmadan parametre paketleriyle çalışmanıza izin verir.

Tabii ki, Sen sadece

void delayed_dispatch() {
  std::apply( func, params );
}

Şimdi, eğer bunu seversek, yazabiliriz:

namespace notstd {
  template<class T>
  constexpr auto tuple_size_v = std::tuple_size<T>::value;
  template<class F, class Tuple>
  decltype(auto) apply( F&& f, Tuple&& tup ) {
    auto indexer = index_upto<
      tuple_size_v<std::remove_reference_t<Tuple>>
    >();
    return indexer(
      [&](auto...Is)->decltype(auto) {
        return std::forward<F>(f)(
          std::get<Is>(std::forward<Tuple>(tup))...
        );
      }
    );
  }
}

nispeten kolay ve temiz olsun sözdizimi gönderilmeye hazır.

void delayed_dispatch() {
  notstd::apply( func, params );
}

Sadece değiştirmek notstdile stdsizin derleyici yükseltmeleri ve bob dayın olduğunda.


std::apply<- kulaklarıma müzik
Flekso

@Flexo Sadece biraz daha kısa index_uptove daha az esnek. ;) funcBağımsız değişkenleri sırasıyla index_uptove ile geriye doğru çağırmayı deneyin std::apply. Kuşkusuz, kim halt bir gruptan geriye doğru bir işlevi çağırmak ister.
Yakk - Adam Nevraumont

Küçük nokta: std::tuple_size_vC ++ 17'dir, bu yüzden değiştirilmesi gereken C ++ 14 çözümü içintypename std::tuple_size<foo>::value
basteln

@basteln Umarım valuebir tür değildir. Ama yine de düzeltildi.
Yakk - Adam Nevraumont

@Yakk Hayır, öyle sizeof...(Types). Ben olmadan çözümünü seviyorum typename.
basteln

3

Sorunu biraz daha düşünerek verilen cevaba dayanarak aynı sorunu çözmenin başka bir yolunu buldum:

template <int N, int M, typename D>
struct call_or_recurse;

template <typename ...Types>
struct dispatcher {
  template <typename F, typename ...Args>
  static void impl(F f, const std::tuple<Types...>& params, Args... args) {
     call_or_recurse<sizeof...(Args), sizeof...(Types), dispatcher<Types...> >::call(f, params, args...);
  }
};

template <int N, int M, typename D>
struct call_or_recurse {
  // recurse again
  template <typename F, typename T, typename ...Args>
  static void call(F f, const T& t, Args... args) {
     D::template impl(f, t, std::get<M-(N+1)>(t), args...);
  }
};

template <int N, typename D>
struct call_or_recurse<N,N,D> {
  // do the call
  template <typename F, typename T, typename ...Args>
  static void call(F f, const T&, Args... args) {
     f(args...);
  }
};

Uygulamanın şu şekilde değiştirilmesini gerektirir delayed_dispatch():

  void delayed_dispatch() {
     dispatcher<Args...>::impl(func, params);
  }

Bu std::tuple, kendi başına bir parametre paketine özyinelemeli olarak dönüştürülerek çalışır . call_or_recursetamamlanan parametre paketini açan gerçek çağrı ile özyinelemeyi sonlandırmak için bir uzmanlık olarak gereklidir.

Bunun "daha iyi" bir çözüm olduğundan emin değilim, ama bunu düşünmenin ve çözmenin başka bir yolu.


enable_ifÖnceki çözümümden tartışmasız daha basit bir şey oluşturmak için kullanabileceğiniz başka bir alternatif çözüm olarak:

#include <iostream>
#include <functional>
#include <tuple>

void f(int a, double b, void* c) {
  std::cout << a << ":" << b << ":" << c << std::endl;
}

template <typename ...Args>
struct save_it_for_later {
  std::tuple<Args...> params;
  void (*func)(Args...);

  template <typename ...Actual>
  typename std::enable_if<sizeof...(Actual) != sizeof...(Args)>::type
  delayed_dispatch(Actual&& ...a) {
    delayed_dispatch(std::forward<Actual>(a)..., std::get<sizeof...(Actual)>(params));
  }

  void delayed_dispatch(Args ...args) {
    func(args...);
  }
};

int main() {
  int a=666;
  double b = -1.234;
  void *c = NULL;

  save_it_for_later<int,double,void*> saved = {
                                 std::tuple<int,double,void*>(a,b,c), f};
  saved.delayed_dispatch();
}

İlk aşırı yükleme, demetten bir argüman daha alır ve bir parametre paketine koyar. İkinci aşırı yük, eşleşen bir parametre paketini alır ve daha sonra ilk çağrı bir birinde devre dışı bırakılır ve sadece ikinci durumun geçerli olacağı durumda yapılır.


1
Bir süre önce buna çok benzer bir şey üzerinde çalıştım. Eğer zamanım varsa, ikinci bir bakacağım ve mevcut cevaplarla nasıl karşılaştırılacağını göreceğim.
Michael Price

@MichaelPrice - tamamen öğrenme perspektifinden, yığın işaretçisini (ya da benzer şekilde konvansiyona özel numaralar çağırıyor) botlama bazı korkunç kesmek kaynamayan herhangi bir alternatif çözümler görmek istiyorum.
Flekso

2

Benim çözüm C ++ 14 std :: index_sequence (ve işlev dönüş türü şablon parametresi RetT olarak) kullanarak Johannes benim varyasyon:

template <typename RetT, typename ...Args>
struct save_it_for_later
{
    RetT (*func)(Args...);
    std::tuple<Args...> params;

    save_it_for_later(RetT (*f)(Args...), std::tuple<Args...> par) : func { f }, params { par } {}

    RetT delayed_dispatch()
    {
        return callFunc(std::index_sequence_for<Args...>{});
    }

    template<std::size_t... Is>
    RetT callFunc(std::index_sequence<Is...>)
    {
        return func(std::get<Is>(params) ...);
    }
};

double foo(int x, float y, double z)
{
  return x + y + z;
}

int testTuple(void)
{
  std::tuple<int, float, double> t = std::make_tuple(1, 1.2, 5);
  save_it_for_later<double, int, float, double> saved (&foo, t);
  cout << saved.delayed_dispatch() << endl;
  return 0;
}

Tüm bu çözümler ilk sorunu çözebilir, ama dürüst olmak gerekirse, bu şablon şeyler basitlik ve sürdürülebilirlik açısından yanlış bir yöne gitmiyor mu?
xy

Bence şablonlar C ++ 11 ve 14 ile çok daha iyi ve daha anlaşılır hale geldi. Birkaç yıl önce kaputun altındaki şablonlarla takviyenin ne yaptığına baktığımda gerçekten cesaretim kırıldı. İyi şablonlar geliştirmenin sadece onları kullanmaktan çok daha zor olduğunu kabul ediyorum.
schwart

1
@xy İlk olarak, şablon karmaşıklığı açısından, bu hiçbir şey değildir . İkincisi, çoğu yardımcı şablon, daha sonra somutlaştırılırken kaydedilen tonlarca zaman için ilk yatırımdır. Son olarak, ne, hangi şablonların yapmanıza izin verdiğini yapma yeteneğiniz olmaz mı? Sadece kullanamazsınız ve diğer programcılara polislik yapıyor gibi görünen alakasız yorumlar bırakamazsınız.
underscore_d
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.