programing tip

Variadic 템플릿 팩 확장

itbloger 2020. 11. 7. 09:03
반응형

Variadic 템플릿 팩 확장


가변 템플릿과 기능을 배우려고합니다. 이 코드가 컴파일되지 않는 이유를 이해할 수 없습니다.

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args)
{
    (bar(args)...);
}

int main()
{
    foo2(1, 2, 3, "3");
    return 0;    
}

컴파일 할 때 오류와 함께 실패합니다.

오류 C3520 : 'args':이 컨텍스트에서 매개 변수 팩을 확장해야합니다.

(함수에서 foo2).


팩 확장이 발생할 수있는 곳 중 하나는 braced-init-list 내부 입니다. 더미 배열의 이니셜 라이저 목록 내부에 확장을 넣어이를 활용할 수 있습니다.

template<typename... Args>
static void foo2(Args &&... args)
{
    int dummy[] = { 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
}

이니셜 라이저의 내용을 더 자세히 설명하려면 :

{ 0, ( (void) bar(std::forward<Args>(args)), 0) ... };
  |       |       |                        |     |
  |       |       |                        |     --- pack expand the whole thing 
  |       |       |                        |   
  |       |       --perfect forwarding     --- comma operator
  |       |
  |       -- cast to void to ensure that regardless of bar()'s return type
  |          the built-in comma operator is used rather than an overloaded one
  |
  ---ensure that the array has at least one element so that we don't try to make an
     illegal 0-length array when args is empty

데모 .

확장의 중요한 장점은 {}왼쪽에서 오른쪽으로 평가할 수 있다는 것입니다.


C ++ 17 접기 표현식을 사용하면 다음과 같이 작성할 수 있습니다.

((void) bar(std::forward<Args>(args)), ...);

매개 변수 팩은 엄격하게 정의 된 컨텍스트 목록에서만 확장 할 수 있으며 연산자 ,는 그중 하나가 아닙니다. 즉, pack 확장을 사용하여 operator로 구분 된 일련의 하위 표현식으로 구성된 표현식을 생성 할 수 없습니다 ,.

경험의 규칙은 "확장은 목록 구분 기호 가있는 -분리 된 패턴 목록생성 할 수 있습니다 ."입니다. 연산자 는 문법적으로 목록을 구성하지 않습니다.,,,

각 인수에 대한 함수를 호출하려면 재귀 (가변 템플릿 프로그래머 상자의 기본 도구)를 사용할 수 있습니다.

template <typename T>
void bar(T t) {}

void foo2() {}

template <typename Car, typename... Cdr>
void foo2(Car car, Cdr... cdr)
{
  bar(car);
  foo2(cdr...);
}

int main()
{
  foo2 (1, 2, 3, "3");
}

라이브 예


SHAMELESS COPY [출처에서 승인 됨]

매개 변수 팩은 엄격하게 정의 된 컨텍스트 목록에서만 확장 할 수 있으며 연산자 ,는 그중 하나가 아닙니다. 즉, pack 확장을 사용하여 operator로 구분 된 일련의 하위 표현식으로 구성된 표현식을 생성 할 수 없습니다 ,.

경험의 규칙은 "확장은 목록 구분 기호가있는 ,-분리 된 패턴 목록을 생성 할 수 있습니다 ,."입니다. 연산자 ,는 문법적으로 목록을 구성하지 않습니다.

각 인수에 대한 함수를 호출하려면 재귀 (가변 템플릿 프로그래머 상자의 기본 도구)를 사용할 수 있습니다.

#include <utility>

template<typename T>
void foo(T &&t){}

template<typename Arg0, typename Arg1, typename ... Args>
void foo(Arg0 &&arg0, Arg1 &&arg1, Args &&... args){
    foo(std::forward<Arg0>(arg0));
    foo(std::forward<Arg1>(arg1), std::forward<Args>(args)...);
}

auto main() -> int{
    foo(1, 2, 3, "3");
}

복사되지 않은 유용한 정보

이 답변에서 보지 못한 또 다른 것은 &&지정자 및 std::forward. C ++에서 &&지정자는 rvalue-references 또는 범용 참조 중 하나를 의미 할 수 있습니다.

나는 rvalue-references에 들어 가지 않고 가변 템플릿으로 작업하는 누군가에게 설명 할 것입니다. 보편적 인 참조는 신이 보낸 것입니다.

완벽한 전달

std::forward범용 참조 의 용도 중 하나는 유형을 다른 함수로 완벽하게 전달하는 것입니다.

In your example, if we pass an int& to foo2 it will be automatically demoted to int because of the signature of the generated foo2 function after template deduction and if you wanted to then forward this arg to another function that would modify it by reference, you will get undesired results (the variable won't be changed) because foo2 will be passing a reference to the temporary created by passing an int to it. To get around this, we specify a forwarding function to take any type of reference to a variable (rvalue or lvalue). Then, to be sure that we pass the exact type passed in the forwarding function we use std::forward, then and only then do we allow the demoting of types; because we are now at the point where it matters most.

If you need to, read more on universal references and perfect forwarding; Scott Meyers is pretty great as a resource.


You can use make_tuple for pack expansion as it introduces a context where the , sequence produced by an expansion is valid

make_tuple( (bar(std::forward<Args>(args)), 0)... );

Now, I suspect the unused/unnamed/temporary tuple of zeroes that's produced is detectable by the compiler and optimized away.

Demo


The C++17 solution to this is really close to your expected code:

template<typename T>
static void bar(T t) {}

template<typename... Args>
static void foo2(Args... args) {
    (bar(args), ...);
}

int main() {
    foo2(1, 2, 3, "3");
    return 0;    
}

This expand the pattern with the comma operator between every expression

// imaginary expanded expression
(bar(1), bar(2), bar(3), bar("3"));

This is a full example, based on the answers here.

Example to reproduce console.log as seen in JavaScript:

Console console;
console.log("bunch", "of", "arguments");
console.warn("or some numbers:", 1, 2, 3);
console.error("just a prank", "bro");

Filename e.g. js_console.h:

#include <iostream>
#include <utility>

class Console {
protected:
    template <typename T>
    void log_argument(T t) {
        std::cout << t << " ";
    }
public:
    template <typename... Args>
    void log(Args&&... args) {
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void warn(Args&&... args) {
        cout << "WARNING: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }

    template <typename... Args>
    void error(Args&&... args) {
        cout << "ERROR: ";
        int dummy[] = { 0, ((void) log_argument(std::forward<Args>(args)),0)... };
        cout << endl;
    }
};

참고URL : https://stackoverflow.com/questions/25680461/variadic-template-pack-expansion

반응형