2017-05-01 1 views
19

이것이 작동하지 않는 이유를 모르겠다. 템플릿과 variadic expression 접기를 이해하는 사람이 무슨 일이 일어나고 있는지 설명하고 작동하는 해결책을 줄 수 있습니까?C++ 17 Variadic Template Folding

#include <iostream> 
#include <string> 

template <typename... Args> 
void print(Args... args) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 
    (std::cout << ... << sep << args) << end; 
} 

int main() 
{ 
    print(1, 2, 3); 
} 

각 args 사이에 공백을 넣고 마지막에 개행을 인쇄해야합니다. sep <<을 제거했지만 인쇄 될 때 각 인수 사이에 공백이없는 경우 작동합니다.

답변

26

Th 진 fold-expressions에 대한 전자 문법 중 하나 여야합니다 : 당신은 무엇

(pack op ... op init) 
(init op ... op pack) 

중 하나를 양식에 맞지 않는, (std::cout << ... << sep << args)이다. (cout << ... << pack)과 같은 것이 필요합니다. 따라서 sep을 삭제해야합니다.

대신 쉼표를 통해 접을 수 있습니다 :

((std::cout << sep << args), ...); 

또는 사용 재귀 :

template <class A, class... Args> 
void print(A arg, Args... args) { 
    std::cout << arg; 
    if constexpr (sizeof...(Args) > 0) { 
     std::cout << sep; 
     print(args...); 
    } 
} 
+0

'(std :: cout << sep << args, ...);에 대해서는 "표현식이 fold expression의 피연산자로 허용되지 않습니다"라고되어 있습니다. 그러나 그것은'((std :: cout << sep << args), ...);'와 같이 그것의 일부분을 괄호로 묶는 것을 제안하지만 첫 번째 인자를 출력하지 않습니다. 작동 할이 버전의 버전이 있습니까? – nickeb96

+0

@ nickeb96 네, 괄호를 잊어 버렸습니다. 나는 당신이 나머지에 관해 무엇을 의미하는지에 관해 명확히하지 않고있다, 그것은 첫번째 논쟁을 건너 뛰지 않는다. – Barry

+1

아, 두 번째 부분이 작동합니다. 그러나 'sep'를 인쇄합니다. 각각의 인수 사이에'sep '를 갖는 직접적인 방법이 있을까요? 아니면 재귀와 같은보다 복잡한 솔루션이 필요합니까? – nickeb96

14

이 작동하지만 후행 공백 출력한다 :이 경우

template <typename... Args> 
void print(Args... args) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 

    ((std::cout << args << sep), ...) << end; 
} 

live wandbox example


를 상기 콤마 연산자 위에 겹은 결과적으로 수행되고 다음과 같은 확장 형식 :

// (pseudocode) 
(std::cout << args<0> << sep), 
(std::cout << args<1> << sep), 
(std::cout << args<2> << sep), 
..., 
(std::cout << args<N> << sep), 
+0

'args <0> '는 실제로 무언가를 수행합니까 아니면 의사 코드의 예제일까요? – nickeb96

+1

@ nickeb96 : 단지 의사 코드. –

10

당신이 정말로 원하는 것은 :

std::string sep = " "; 
std::string end = "\n"; 
(std::cout << ... << (sep << args)) << end; 

당신이 원하기 때문에 (sep << args)은 왼쪽 접이식으로 std::cout입니다. sep << argsstd::cout으로 스트리밍되거나 전혀 스트리밍되지 않는다는 것을 모르기 때문에이 방법은 작동하지 않습니다. <<은 왼쪽이 스트림 인 경우에만 스트리밍됩니다.

간단히 말해서 문제는 sep << args이 스트리밍 중이라는 것을 이해하지 못한다는 것입니다.

다른 문제는 람다가 아닙니다.

이 문제를 해결할 수 있습니다.

template<class F> 
struct ostreamer_t { 
    F f; 
    friend std::ostream& operator<<(std::ostream& os, ostreamer_t&& self) { 
     self.f(os); 
     return os; 
    } 
    template<class T> 
    friend auto operator<<(ostreamer_t self, T&& t) { 
     auto f = [g = std::move(self.f), &t](auto&& os)mutable { 
      std::move(g)(os); 
      os << t; 
     }; 
     return ostreamer_t<decltype(f)>{std::move(f)}; 
    } 
}; 

struct do_nothing_t { 
    template<class...Args> 
    void operator()(Args&&...)const {} 
}; 

const ostreamer_t<do_nothing_t> ostreamer{{}}; 

template <typename... Args> 
void print(Args... args) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 
    (std::cout << ... << (ostreamer << sep << args)) << end; 
} 

live example. (나는 또한 직각으로 작업 할 수 있도록 sep에 리터럴을 사용했습니다.)

ostreamer<< '일 때 참조를 캡처 한 다음 다시 덤프하면 <<에서 ostream으로 바뀝니다.

이 전체 프로세스는 컴파일러에게 투명해야하므로 적절한 최적화 프로그램이 모든 것을 증발해야합니다.

3

다른 사람이 대답 한대로 접기 식 형식을 잘못 사용하려고합니다. 당신은 매우 간단한 방법으로 당신의 목적을 위해 람다 도우미를 사용할 수 있습니다

template <typename... Args> 
void print(Args&&... args) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) { 
     std::cout << sep; 
     return arg; 
    }; 
    (std::cout << ... << streamSep(args)) << end; 
} 

이것은 당신이 쓴 코드에서 예상되는 동작을 따릅니다. 첫 번째 인수하기 전에 9월을 피하려는 경우에는, 당신이 사용할 수있는 다음 :

template <typename Arg, typename... Args> 
void print(Arg&& arg, Args&&... args) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 
    auto streamSep = [&sep](const auto& arg) -> decltype(arg) { 
     std::cout << sep; 
     return arg; 
    }; 
    std::cout << arg; 
    (std::cout << ... << streamSep(args)) << end; 
} 
+0

나는 람다에 대한 전문가는 아니지만, 가상 함수로 람다를 함수로 만들고 프린트 함수 바로 위에 놓을 수 있습니까? 아니면 더 복잡하고 악화 된 어셈블리를 생성합니까? – nickeb96

+1

@ nickeb96 우리가이 예제를 최적화하려는 아이디어에서 시작한다면 (두 개의 _unoptimized_ 매개 변수 _sep_ 및 _end_를 가지고 있기 때문에 어떻게 처리해야하는지 잘 모르겠습니다), 그러면 같은 어셈블리를 쉽게 얻을 수 있습니다 (GCC 7.1에) 람다 대신에 함수를 사용한다. [이 예제에서] (https://godbolt.org/g/TGoYLT)를보십시오. 두 버전은 USE_LAMBDA를 정의하는지 여부를 정의하여 컴파일 할 수 있으며 결과가 동일하다는 것을 알 수 있습니다. 그러나 _sep_ 및 _end_를 리터럴로 사용했기 때문에 의심 스럽다고 대답하지 못했습니다. – dodomorandi

1

당신은 람다 세퍼레이터는 사이에 삽입되는 보장이

template <typename... Args> 
void print(Args... args) 
{ 
    bool first = true; 
    auto lambda = [&](auto param) 
    { 
    if(!first) std::cout << ','; 
    first= false; 
    return param; 
    }; 

    ((std::cout << lambda(args)), ...); 
} 

처럼 뭔가를 시도 할 수 있습니다 두 가지 항목. 한편

당신이 템플릿을 오버로드 할 수 싶어 사용 람다를 해달라고하면 :

template<typename T> 
void print(T item) 
{ 
    std::cout << item; 
} 

template<typename T, typename... Args> 
void print(T item, Args... args) 
{ 
    print(item); 
    std::cout << ','; 
    print(args...); 
} 
1

당신은 sep 후행/선도하지 않으려면 :

template <typename First, typename... Rest> 
void print(First first, Rest... rest) 
{ 
    std::string sep = " "; 
    std::string end = "\n"; 

    std::cout << first; 
    ((std::cout << sep << rest), ...); 
    std::cout << end; 
} 

당신은 std::cout << end;을해야 하나의 매개 변수로 대소 문자를 처리하는 별도의 명령.