Variadic 템플릿 팩 확장


78

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

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).


1
만약에하면 Args비어?
CoffeeandCode

답변:


129

팩 확장이 발생할 수있는 곳 중 하나는 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)), ...);

19
내가 본 한 가지 변형 using expander = int[]; expander{...};은 배열 변수에 이름 이 없기 때문에 배열이 내가 만들거나 사용할 필요가 없다는 것이 컴파일러에게 노골적으로 명백해지기 때문입니다.
Mooing Duck 2014 년

4
두 번째 0는 무엇입니까? 단지 쉼표 연산자 후 한
아론 McDaid

4
@AaronMcDaid 전체 표현식 int의 유형이 배열의 요소 유형과 일치하도록합니다.
TC

나는 사람이 operator void ()(예, 화가 나, 알아) 쓸 수 있다는 것을 알아 차렸다 . 하지만 아니요, 캐스팅 void이 나쁜 생각 이라고 말하는 것은 아닙니다 . 그냥 최소한의 부작용 팩을 확장하려고 할 때 발생할 수 미친 것들에 대한 재미있는 생각을 가지고
아론 McDaid

2
@AaronMcDaid (void)operator void() (고맙게도) 호출하지 않습니다 .
TC

40

매개 변수 팩은 엄격하게 정의 된 컨텍스트 목록에서만 확장 할 수 있으며 연산자 ,는 그중 하나가 아닙니다. 즉, 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");
}

라이브 예


2
젠장, 당신은이 흔들리는 주먹 에 대답하기 위해 나에게 내기를 걸 었지만 당신의 대답에 완벽한 전달을 추가해야 할 것입니다 이것은 또한 "가변 템플릿 프로그래머 상자의 기본 도구"입니다.
CoffeeandCode

답변 주셔서 감사합니다. 재귀 구현에 대해 알고 있습니다. 재귀 및 새 기능없이 코드를 컴파일하는 해결 방법을 찾고 싶습니다.
Viacheslav Dronov 2014 년

1
@ViacheslavDronov는 템플릿을 사용하는 것처럼보고 있습니다. 컴파일러에 의해 생성되는 함수의 쓰레기가 이미 있습니다. 그 목록에 하나를 추가하는 것은 어떻습니까?
CoffeeandCode

@CoffeeandCode 나는 공식적으로 내 대답을 복사하고 완벽한 전달을 추가하고 설명하여 확장 할 수있는 권한을 부여합니다.
문헌 : Angew는 더 이상 자랑 SO의없는

5
더미 배열로 쉽게 확장 할 수 있습니다. int dummy[] = { 0, ((void) bar(std::forward<Args>(args)),0)... };.
TC

17

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범용 참조 의 용도 중 하나는 유형을 다른 함수로 완벽하게 전달하는 것입니다.

당신의 예에서 우리가 통과 할 경우, int&foo2자동으로 강등 될 것입니다 int때문에 생성의 서명의 foo2템플릿 공제 후 기능은 다음 앞으로이 원한다면 arg참조하여 수정하는 것이 다른 기능에, 당신은 (원치 않는 결과를 얻을 것이다 변수는 변경되지 않습니다) foo2를 전달하여 생성 된 임시에 대한 참조를 전달 int하기 때문입니다. 이 문제를 해결 하기 위해 변수 (rvalue 또는 lvalue) 에 대한 모든 유형의 참조 를 가져 오는 전달 함수를 지정합니다 . 그런 다음, 우리는 우리가 사용하는 전달 함수에 전달 된 정확한 유형을 통과하는지 확인하기 위해 다음과, std::forward그런 다음 유형의 강등을 허용합니까? 우리는 지금 가장 중요한 시점에 있기 때문입니다.

필요한 경우 범용 참조완벽한 전달 에 대해 자세히 읽어보십시오 . Scott Meyers는 리소스로서 꽤 훌륭합니다.


6

이에 대한 C ++ 17 솔루션은 예상 코드와 매우 유사합니다.

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

이것은 모든 표현식 사이에 쉼표 연산자로 패턴을 확장합니다.

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

4

make_tuple확장에 ,의해 생성 된 시퀀스가 ​​유효한 컨텍스트를 도입하므로 팩 확장에 사용할 수 있습니다.

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

이제 생성 된 사용되지 않는 / 이름이 지정되지 않은 / 일시적인 제로 튜플이 컴파일러에 의해 감지되고 최적화 된 것으로 의심됩니다.

데모


이것은 어떤 주문 bar이 호출 되는지에 대한 보장을하지 않습니다 .
Eric

1

이것은 여기에 대한 답변을 기반으로 한 전체 예입니다.

console.logJavaScript에서 볼 수있는 재현 예제 :

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

파일 이름 예 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;
    }
};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.