Làm thế nào để lưu trữ các đối số mẫu khác nhau?


89

Có thể lưu trữ một gói tham số nào đó để sử dụng sau này không?

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
    }
}

Sau đó, sau này:

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

    //...

    add.act();
}

3
Đúng; lưu trữ một bộ giá trị và sử dụng một số loại gói chỉ mục để thực hiện cuộc gọi.
Kerrek SB

Điều này có trả lời câu hỏi của bạn không? C ++ Cách lưu trữ một gói tham số dưới dạng một biến
user202729

Câu trả lời:


67

Để hoàn thành những gì bạn muốn ở đây, bạn sẽ phải lưu trữ các đối số mẫu của mình trong một bộ:

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

Hơn nữa, bạn sẽ phải thay đổi hàm tạo của mình một chút. Đặc biệt, khởi tạo argsbằng một std::make_tuplevà cũng cho phép các tham chiếu phổ biến trong danh sách tham số của bạn:

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

Hơn nữa, bạn sẽ phải thiết lập một trình tạo trình tự giống như sau:

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...> {};
}

Và bạn có thể triển khai phương pháp của mình theo cách lấy một trình tạo như vậy:

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);
}

Và rằng nó! Vì vậy, bây giờ lớp của bạn sẽ trông như thế này:

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);
    }
};

Đây là chương trình đầy đủ của bạn trên Coliru.


Cập nhật: Đây là một phương thức trợ giúp mà thông số kỹ thuật của các đối số mẫu không cần thiết:

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();
}

Và một lần nữa, đây là một bản demo khác.


1
bạn có thể mở rộng điều đó một chút trong câu trả lời của mình không?
Eric B

1
Vì Ts ... là tham số mẫu lớp, không phải tham số mẫu hàm, Ts && ... không xác định gói tham chiếu phổ quát vì không có loại trừ nào xảy ra cho gói tham số. @jogojapan hiển thị cách chính xác để đảm bảo bạn có thể chuyển các tham chiếu phổ quát đến hàm tạo.
viêm vú,

3
Xem ra để tham khảo và thời gian tồn tại của đối tượng! void print(const std::string&); std::string hello(); auto act = make_action(print, hello());là không tốt. Tôi thích hành vi của std::bind, tạo một bản sao của mỗi đối số trừ khi bạn vô hiệu hóa điều đó bằng std::refhoặc std::cref.
aschepler

1
Tôi nghĩ @jogojapan có một cách giải pháp ngắn gọn và dễ đọc hơn.
Tim Kuipers

1
@Riddick Thay đổi args(std::make_tuple(std::forward<Args>(args)...))thành args(std::forward<Args>(args)...). BTW Tôi đã viết điều này một thời gian dài và tôi sẽ không sử dụng mã này cho mục đích ràng buộc một hàm với một số đối số. Tôi sẽ chỉ sử dụng std::invoke()hoặc std::apply()ngày nay.
0x499602D2

23

Bạn có thể sử dụng std::bind(f,args...)cho việc này. Nó sẽ tạo ra một đối tượng có thể di chuyển và có thể sao chép để lưu trữ bản sao của đối tượng hàm và của từng đối số để sử dụng sau này:

#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;
}

Lưu ý rằng đó std::bindlà một hàm và bạn cần lưu trữ, với tư cách là thành viên dữ liệu, kết quả của việc gọi nó. Kiểu dữ liệu của kết quả đó không dễ dự đoán (Tiêu chuẩn thậm chí không chỉ định chính xác), vì vậy tôi sử dụng kết hợp decltypestd::declvalđể tính toán kiểu dữ liệu đó tại thời điểm biên dịch. Xem định nghĩa của Action::bind_typeở trên.

Cũng lưu ý cách tôi đã sử dụng các tham chiếu phổ quát trong hàm tạo mẫu. Điều này đảm bảo rằng bạn có thể truyền các đối số không khớp T...chính xác với các tham số mẫu lớp (ví dụ: bạn có thể sử dụng các tham chiếu rvalue cho một số Tvà bạn sẽ nhận được chúng được chuyển tiếp nguyên trạng đến bindcuộc gọi.)

Lưu ý cuối cùng: Nếu bạn muốn lưu trữ các đối số dưới dạng tham chiếu (để hàm bạn truyền vào có thể sửa đổi, thay vì chỉ sử dụng chúng), bạn cần sử dụng std::refđể bọc chúng trong các đối tượng tham chiếu. Chỉ chuyển một T &sẽ tạo ra một bản sao của giá trị, không phải là một tham chiếu.

Mã hoạt động trên Coliru


Nó không nguy hiểm để ràng buộc các giá trị? Những điều đó sẽ không bị vô hiệu khi addđược xác định trong một phạm vi khác sau đó act()được gọi là đâu? Hàm tạo không nên nhận ConstrT&... argshơn là ConstrT&&... args?
Tim Kuipers

1
@Angelorf Xin lỗi vì tôi đã trả lời muộn. Bạn có nghĩa là giá trị trong cuộc gọi đến bind()? Vì bind()được đảm bảo tạo bản sao (hoặc di chuyển vào các đối tượng mới được tạo), tôi không nghĩ rằng có thể có vấn đề.
jogojapan

@jogojapan Lưu ý nhanh, MSVC17 yêu cầu hàm trong hàm tạo cũng phải được chuyển tiếp tới bind_ (tức là bind_ (std :: forward <std :: function <void (T ...) >> (f), std :: forward < ConstrT> (args) ...))
Ra mắt

1
Trong trình khởi tạo, bind_(f, std::forward<ConstrT>(args)...)là hành vi không xác định theo tiêu chuẩn, vì phương thức khởi tạo đó được xác định thực thi. bind_typeđược chỉ định là bản sao và / hoặc có thể di chuyển được, tuy nhiên, vì vậy bind_{std::bind(f, std::forward<ConstrT>(args)...)}vẫn sẽ hoạt động.
joshtch

5

Câu hỏi này là từ C ++ 11 ngày. Nhưng đối với những người tìm thấy nó trong kết quả tìm kiếm bây giờ, một số cập nhật:

Một std::tuplethành viên vẫn là cách đơn giản để lưu trữ các lập luận nói chung. (Một std::bindgiải pháp tương tự như @ jogojapan cũng sẽ hoạt động nếu bạn chỉ muốn gọi một hàm cụ thể, nhưng không hoạt động nếu bạn muốn truy cập các đối số theo những cách khác hoặc truyền các đối số cho nhiều hơn một hàm, v.v.)

Trong C ++ 14 trở lên, std::make_index_sequence<N>hoặcstd::index_sequence_for<Pack...> có thể thay thế helper::gen_seq<N>công cụ được thấy trong giải pháp của 0x499602D2 :

#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...>{});
    }
    // ...
};

Trong C ++ 17 trở lên, std::applycó thể được sử dụng để giải nén bộ tuple:

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

Đây là một chương trình C ++ 17 đầy đủ cho thấy việc triển khai đơn giản. Tôi cũng đã cập nhật make_actionđể tránh các loại tham chiếu trong tuple, luôn không tốt cho các đối số rvalue và khá rủi ro cho các đối số lvalue.


3

Tôi nghĩ rằng bạn có một vấn đề XY. Tại sao lại gặp khó khăn khi lưu trữ gói tham số khi bạn chỉ có thể sử dụng lambda tại trang web gọi? I E,

#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();
}

Bởi vì trong ứng dụng của tôi, một hành động thực sự có 3 functors khác nhau mà tất cả đều liên quan, và tôi thà lớp chứa nó chứa 1 hành động, chứ không phải 3 std :: Chức năng
Eric B
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.