가변 템플릿 인수를 저장하는 방법은 무엇입니까?


89

나중에 사용하기 위해 어떻게 든 매개 변수 팩을 저장할 수 있습니까?

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

그런 다음 나중에 :

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

    //...

    add.act();
}

3
예; 튜플을 저장하고 일종의 인덱스 팩을 사용하여 호출합니다.
Kerrek SB

이것이 귀하의 질문에 대답합니까? C ++ 매개 변수 팩을 변수로 저장하는 방법
user202729

답변:


67

여기서 원하는 작업을 수행하려면 템플릿 인수를 튜플에 저장해야합니다.

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

또한 생성자를 약간 변경해야합니다. 특히로 초기화 args하고 std::make_tuple매개 변수 목록에서 범용 참조를 허용합니다.

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

또한 다음과 같이 시퀀스 생성기를 설정해야합니다.

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

그리고 이러한 생성기를 사용하는 방식으로 메서드를 구현할 수 있습니다.

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

그리고 그것! 이제 수업은 다음과 같이 보일 것입니다.

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

다음은 Coliru에 대한 전체 프로그램입니다.


업데이트 : 다음은 템플릿 인수를 지정할 필요가없는 도우미 메서드입니다.

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

그리고 또 다른 데모가 있습니다.


1
답변에서 조금 더 확장 할 수 있습니까?
Eric B

1
Ts ...는 함수 템플릿 매개 변수가 아니라 클래스 템플릿 매개 변수이므로 Ts && ...는 매개 변수 팩에 대해 유형 추론이 발생하지 않으므로 범용 참조 팩을 정의하지 않습니다. @jogojapan은 생성자에 대한 범용 참조를 전달할 수있는 올바른 방법을 보여줍니다.
masrtis 2013

3
참조 및 개체 수명에주의하십시오! void print(const std::string&); std::string hello(); auto act = make_action(print, hello());좋지 않다. 또는로 std::bind비활성화하지 않는 한 각 인수의 복사본을 만드는 의 동작을 선호합니다 . std::refstd::cref
aschepler

1
@jogojapan에는 더 간결하고 읽기 쉬운 솔루션이 있다고 생각합니다.
Tim Kuipers

1
@Riddick 변경 args(std::make_tuple(std::forward<Args>(args)...))args(std::forward<Args>(args)...). BTW 나는 오래 전에 이것을 작성했으며 함수를 일부 인수에 바인딩하는 목적 으로이 코드를 사용하지 않을 것입니다. 나는 std::invoke()또는 std::apply()요즘 사용 합니다.
0x499602D2

23

std::bind(f,args...)이것을 위해 사용할 수 있습니다 . 나중에 사용하기 위해 함수 개체 및 각 인수의 복사본을 저장하는 이동 가능하고 복사 가능한 개체를 생성합니다.

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

공지 사항이 std::bind함수와 데이터 멤버를 호출 한 결과로 저장해야합니다. 그 결과의 데이터 유형은 예측하기 쉽지 않습니다 (표준에서는 정확하게 지정하지도 않습니다). 따라서 컴파일 타임 에 decltype및 의 조합을 사용하여 std::declval해당 데이터 유형을 계산합니다. Action::bind_type위 의 정의를 참조하십시오 .

또한 템플릿 생성자에서 범용 참조를 어떻게 사용했는지 주목하십시오. 이렇게하면 클래스 템플릿 매개 변수 T...와 정확히 일치하지 않는 인수를 전달할 수 있습니다 (예 : 일부에 rvalue 참조를 사용할 수 T있으며 bind호출에 그대로 전달됩니다 ).

마지막 참고 : 인수를 참조로 저장하려면 (전달하는 함수가 단순히 사용하는 것이 아니라 수정할 수 있도록)를 사용 std::ref하여 참조 객체로 래핑해야합니다. 단순히를 전달하면 T &참조가 아닌 값의 복사본이 생성됩니다.

Coliru의 운영 코드


rvalue를 바인딩하는 것은 위험하지 않습니까? add다른 스코프에서 정의 되었을 때 그것들은 무효화되지 않을까요 act()? 생성자가 ConstrT&... args아니라 가져와야하지 ConstrT&&... args않습니까?
Tim Kuipers

1
@Angelorf 답장을 늦게 죄송합니다. 에 대한 호출에서 rvalue를 의미 bind()합니까? bind()복사본을 만들거나 새로 만든 개체로 이동하는 것이 보장 되기 때문에 문제가 없다고 생각합니다.
jogojapan

@jogojapan 빠른 메모, MSVC17은 생성자의 함수를 bind_로 전달해야합니다 (예 : bind_ (std :: forward <std :: function <void (T ...) >> (f), std :: forward < ConstrT> (args) ...))
Outshined

1
이니셜 라이저에서는 bind_(f, std::forward<ConstrT>(args)...)생성자가 구현 정의되어 있으므로 표준에 따라 정의되지 않은 동작입니다. bind_type복사 및 / 또는 이동 구성이 가능하도록 지정되어 있으므로 bind_{std::bind(f, std::forward<ConstrT>(args)...)}여전히 작동합니다.
joshtch

5

이 질문은 C ++ 11 일에서 나왔습니다. 그러나 지금 검색 결과에서 찾은 사람들을 위해 몇 가지 업데이트가 있습니다.

std::tuple회원은 아직 일반적으로 인수를 저장하는 간단한 방법입니다. ( @jogojapanstd::bind 과 유사한 솔루션 은 특정 함수를 호출하려는 경우에도 작동하지만 다른 방식으로 인수에 액세스하거나 둘 이상의 함수에 인수를 전달하려는 경우에는 작동하지 않습니다.)

C ++ 14 std::make_index_sequence<N>이상std::index_sequence_for<Pack...> 에서 또는 0x499602D2의 솔루션helper::gen_seq<N>표시된 도구를 대체 할 수 있습니다 .

#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 이상 std::apply에서는 튜플의 압축을 푸는 데 사용할 수 있습니다.

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

다음 은 단순화 된 구현을 보여주는 전체 C ++ 17 프로그램 입니다. 또한 make_action에서 참조 유형을 피하기 위해 업데이트 tuple했는데, 이는 항상 rvalue 인수에 대해 나쁘고 lvalue 인수에 대해 상당히 위험했습니다.


3

XY 문제가 있다고 생각합니다. 호출 사이트에서 람다를 사용할 수 있는데 왜 매개 변수 팩을 저장하는 데 문제가 있습니까? 즉,

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

내 응용 프로그램에서 Action에는 실제로 모두 관련된 3 개의 다른 펑터가 있고, 3 std :: function이 아닌 1 Action을 포함하는 클래스를
Eric B
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.