Değişken şablon argümanları nasıl saklanır?


89

Bir parametre paketini daha sonra kullanmak için bir şekilde saklamak mümkün müdür?

template <typename... T>
class Action {
private:        
    std::function<void(T...)> f;
    T... args;  // <--- something like this
public:
    Action(std::function<void(T...)> f, T... args) : f(f), args(args) {}
    void act(){
        f(args);  // <--- such that this will be possible
    }
}

Daha sonra:

void main(){
    Action<int,int> add([](int x, int y){std::cout << (x+y);}, 3, 4);

    //...

    add.act();
}

3
Evet; bir demet saklayın ve aramayı yapmak için bir tür dizin paketi kullanın.
Kerrek SB 01

Yanıtlar:


67

Burada yapmak istediğinizi başarmak için şablon argümanlarınızı bir demet halinde saklamanız gerekir:

std::tuple<Ts...> args;

Dahası, kurucunuzu biraz değiştirmeniz gerekecek. Özellikle, başlatılırken argsbir ile std::make_tupleve ayrıca parametre listesinde evrensel referanslar izin:

template <typename F, typename... Args>
Action(F&& func, Args&&... args)
    : f(std::forward<F>(func)),
      args(std::forward<Args>(args)...)
{}

Dahası, şuna çok benzer bir dizi oluşturucu kurmanız gerekir:

namespace helper
{
    template <int... Is>
    struct index {};

    template <int N, int... Is>
    struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

    template <int... Is>
    struct gen_seq<0, Is...> : index<Is...> {};
}

Ve yönteminizi, böyle bir jeneratör alan biri açısından uygulayabilirsiniz:

template <typename... Args, int... Is>
void func(std::tuple<Args...>& tup, helper::index<Is...>)
{
    f(std::get<Is>(tup)...);
}

template <typename... Args>
void func(std::tuple<Args...>& tup)
{
    func(tup, helper::gen_seq<sizeof...(Args)>{});
}

void act()
{
   func(args);
}

Ve işte bu! Yani şimdi sınıfınız şöyle görünmeli:

template <typename... Ts>
class Action
{
private:
    std::function<void (Ts...)> f;
    std::tuple<Ts...> args;
public:
    template <typename F, typename... Args>
    Action(F&& func, Args&&... args)
        : f(std::forward<F>(func)),
          args(std::forward<Args>(args)...)
    {}

    template <typename... Args, int... Is>
    void func(std::tuple<Args...>& tup, helper::index<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, helper::gen_seq<sizeof...(Args)>{});
    }

    void act()
    {
        func(args);
    }
};

İşte Coliru'daki tam programınız.


Güncelleme: Burada, şablon bağımsız değişkenlerinin belirtiminin gerekli olmadığı bir yardımcı yöntem verilmiştir:

template <typename F, typename... Args>
Action<Args...> make_action(F&& f, Args&&... args)
{
    return Action<Args...>(std::forward<F>(f), std::forward<Args>(args)...);
}

int main()
{
    auto add = make_action([] (int a, int b) { std::cout << a + b; }, 2, 3);

    add.act();
}

Ve yine, işte başka bir demo.


1
Cevabınızda bunu biraz açıklar mısınız?
Eric B

1
Ts ... bir sınıf şablon parametresi olduğundan, bir işlev şablon parametresi olmadığından, Ts && ... parametre paketi için meydana gelen bir tür kesintisi olmadığından evrensel referanslar paketini tanımlamaz. @jogojapan, evrensel referansları kurucuya iletebilmenizi sağlamanın doğru yolunu gösterir.
masrtis

3
Referanslara ve nesne yaşam sürelerine dikkat edin! void print(const std::string&); std::string hello(); auto act = make_action(print, hello());iyi değil. Veya std::bindile devre dışı bırakmadığınız sürece her argümanın bir kopyasını oluşturan davranışını tercih ederim . std::refstd::cref
aschepler

1
@Jogojapan'ın çok daha özlü ve okunabilir bir çözümü olduğunu düşünüyorum.
Tim Kuipers

1
@Riddick Değişim args(std::make_tuple(std::forward<Args>(args)...))için args(std::forward<Args>(args)...). BTW Bunu uzun zaman önce yazdım ve bu kodu bir işlevi bazı argümanlara bağlamak amacıyla kullanmazdım. Sadece std::invoke()ya da std::apply()bugünlerde kullanırdım .
0x499602D2

23

Bunun için kullanabilirsiniz std::bind(f,args...). İşlev nesnesinin ve her bir bağımsız değişkenin daha sonra kullanılmak üzere bir kopyasını depolayan taşınabilir ve muhtemelen kopyalanabilir bir nesne oluşturacaktır:

#include <iostream>
#include <utility>
#include <functional>

template <typename... T>
class Action {
public:

  using bind_type = decltype(std::bind(std::declval<std::function<void(T...)>>(),std::declval<T>()...));

  template <typename... ConstrT>
  Action(std::function<void(T...)> f, ConstrT&&... args)
    : bind_(f,std::forward<ConstrT>(args)...)
  { }

  void act()
  { bind_(); }

private:
  bind_type bind_;
};

int main()
{
  Action<int,int> add([](int x, int y)
                      { std::cout << (x+y) << std::endl; },
                      3, 4);

  add.act();
  return 0;
}

Bunun std::bindbir işlev olduğuna ve onu çağırmanın sonucunu veri üyesi olarak saklamanız gerektiğine dikkat edin . Bu sonucun veri türünü tahmin etmek kolay değildir (Standart bunu tam olarak belirtmez bile), bu yüzden bir decltypevestd::declval derleme zamanında bu veri tipini hesaplamak için . Action::bind_typeYukarıdaki tanıma bakın .

Ayrıca templated yapıcıda evrensel referansları nasıl kullandığıma dikkat edin. Bu, sınıf şablonu parametreleriyle T...tam olarak eşleşmeyen argümanları iletebilmenizi sağlar (örneğin, bazılarına rvalue referansları kullanabilir Tve bunları olduğu gibi yönlendirirsiniz.bind çağrıya yönlendirebilirsiniz.)

Son not: Bağımsız değişkenleri referans olarak saklamak istiyorsanız (böylece ilettiğiniz işlev, yalnızca kullanmak yerine değiştirebilir), std::refbunları referans nesnelerine sarmak için kullanmanız gerekir . Yalnızca a geçmek T &, referans değil değerin bir kopyasını oluşturur.

Coliru'da operasyonel kod


Değerleri bağlamak tehlikeli değil mi? addFarklı bir kapsamda tanımlandığında, o zaman nereden act()çağrılırsa bunlar geçersiz kılınmaz mı? Yapıcı ConstrT&... argsyerine almamalı ConstrT&&... argsmı?
Tim Kuipers

1
@Angelorf Geç cevabım için özür dilerim. Çağrıda r değerleri mi demek bind()istiyorsun? Yana bind()kopyalarını yapmak (ya da taze oluşturulan nesnelere taşınmak) garanti, ben bir sorun olabilir sanmıyorum.
jogojapan

@jogojapan Hızlı not, MSVC17, yapıcıdaki işlevin de bind_'ye iletilmesini gerektirir (yani bind_ (std :: forward <std :: function <void (T ...) >> (f), std :: forward < ConstrT> (args) ...))
Outshined

1
Başlatıcıda, bind_(f, std::forward<ConstrT>(args)...)bu yapıcı uygulama tanımlı olduğundan, standarda göre tanımsız bir davranıştır. bind_typekopya ve / veya hareket ettirilebilir olarak belirtilir, ancak bind_{std::bind(f, std::forward<ConstrT>(args)...)}yine de çalışmalıdır.
joshtch

6

Bu soru C ++ 11 günlerindendi. Ancak şimdi arama sonuçlarında bulanlar için bazı güncellemeler:

Bir std::tupleüye, argümanları genel olarak saklamanın hala kolay yoludur. ( @ Jogojapan uygulamasınastd::bind benzer bir çözüm , yalnızca belirli bir işlevi çağırmak istiyorsanız da işe yarar, ancak bağımsız değişkenlere başka yollarla erişmek veya bağımsız değişkenleri birden fazla işleve iletmek, vb.)

C ++ 14 ve sonraki sürümlerde std::make_index_sequence<N>veya std::index_sequence_for<Pack...>0x499602D2'nin çözümündehelper::gen_seq<N> görülen aracı değiştirebilir :

#include <utility>

template <typename... Ts>
class Action
{
    // ...
    template <typename... Args, std::size_t... Is>
    void func(std::tuple<Args...>& tup, std::index_sequence<Is...>)
    {
        f(std::get<Is>(tup)...);
    }

    template <typename... Args>
    void func(std::tuple<Args...>& tup)
    {
        func(tup, std::index_sequence_for<Args...>{});
    }
    // ...
};

C ++ 17 ve sonraki sürümlerde, std::applytuple'ın paketini açmak için kullanılabilir:

template <typename... Ts>
class Action
{
    // ...
    void act() {
        std::apply(f, args);
    }
};

İşte basitleştirilmiş uygulamayı gösteren tam bir C ++ 17 programı . Ayrıca , rvalue argümanları için her zaman kötü olan ve lvalue argümanları için oldukça riskli olan make_actionreferans türlerinden kaçınmak için güncelledim tuple.


3

Sanırım bir XY sorununuz var. Çağrı yerinde sadece bir lambda kullanabilecekken neden parametre paketini saklamak için bu kadar zahmete giresiniz? yani

#include <functional>
#include <iostream>

typedef std::function<void()> Action;

void callback(int n, const char* s) {
    std::cout << s << ": " << n << '\n';
}

int main() {
    Action a{[]{callback(13, "foo");}};
    a();
}

Çünkü benim uygulamamda, bir Eylemin aslında birbiriyle ilişkili 3 farklı functoru vardır ve onu içeren sınıfların 3 std :: function değil, 1 Action içermesini tercih ederim
Eric B
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.