2011-05-13 5 views
3

첫 번째 매개 변수 유형에서 오버로드하려는 variadic 함수가 있습니다.가변 템플릿 함수 ostream에서 오버로드

void write(void) { } 

void write(std::ostream&) { } 

template< typename Head, typename... Rest > 
void write(std::ostream& out, Head&& head, Rest&&... rest) 
{ 
    out << head; 
    write(out, std::forward<Rest>(rest)...); 
} 

template< typename... Args > 
void write(Args&&... args) 
{ 
    write(std::cout, std::forward<Args>(args)...); 
} 

그러나이 기능은 예상대로 동작하지 않습니다.

write("## to cout ##"); // printed to stdout as expected 
write(std::cerr, "## to cerr ##"); // printed to stderr as expected 
std::ostringstream oss; 
write(oss, "## to string ##"); // error here 
// '0x7fff9db8## to string ##' is printed to stdout! 

여기에 어떤 현상이 발생합니까?
과부하 해결로 원하는 기능을 선택하지 않는 이유는 무엇입니까?
메타 프로그래밍을 많이하지 않고이를 수행 할 수있는 방법이 있습니까? (나는 이것을 std::is_convertible으로 해결할 수 있었지만, 솔루션은 위에 표시된 간단한 코드보다 훨씬 컸다.)

+0

왜 마지막 템플릿 전문화가 필요합니까? 문제는 그 문제없이 사라질 것입니다. –

+0

@Diego, 마지막 전문화 ('write (args)')는 호출자가 ostream 객체를 제공하지 않은 경우 첫 번째 인수로'std :: cout'을 넣습니다. –

답변

5

다른 서식 파일로 전달할 때 ostringstreamostream으로 기본 변환이 필요하고, write(std::cout, ...)으로 전달하는 서식 파일로 전달하면 변환 할 필요가 없습니다. 따라서 ostringstream을 전달하면 ostringstream을 인수로 전달하여보다 구체적인 템플릿으로 출력하는보다 일반적인 템플릿을 선택합니다. ostringstream을 출력하면 void*으로 변환되어 인쇄됩니다.

is_base_of으로 해결할 수 있습니다 (is_convertible을 사용하는 것보다 나에게 잘 느껴짐).

template<typename Arg, typename... Args, typename = 
    typename std::enable_if< 
    !std::is_base_of< 
     std::ostream, 
     typename std::remove_reference<Arg>::type, 
     >::value>::type 
> 
void write(Arg&& arg, Args&&... args) 
{ 
    write(std::cout, std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

나는 개인적으로 내가 꺾쇠 괄호 일정 수준에 대처할 수 없기 때문에, 내 코드에 너무 많은 SFINAE를 사용하여 싫어한다. 그래서 당신은, 그래서 첫 번째 인수로, 그것은 ostream의 이러한 좌변에 전화를 변화시킬 write_dispatch를 호출합니다 ostream의 좌변이 아닌 다른 뭔가를 호출하는 경우,

template< typename Arg, typename... Args > 
void write_dispatch(std::true_type, Arg&& arg, Args&&... args) 
{ 
    std::ostream& os = arg; 
    write(os, std::forward<Args>(args)...); 
} 

template< typename Arg, typename... Args > 
void write_dispatch(std::false_type, Arg&& arg, Args&&... args) 
{ 
    write(std::cout, std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

template< typename Arg, typename... Args > 
void write(Arg&& arg, Args&&... args) 
{ 
    typedef typename std::remove_reference<Arg>::type nonref_type; 
    write_dispatch(std::is_base_of<std::ostream, nonref_type>(), 
      std::forward<Arg>(arg), std::forward<Args>(args)...); 
} 

이 방법은 오버로드를 사용하려면 다른 write 템플릿을 계속 사용할 수 있습니다.

마지막으로, out << std::forward<Head>(head)이라고 말해야합니다. 그렇지 않으면 결국 모든 반복 작업 단계에서 std::forward을 사용할 수 있습니다. 왜냐하면 결국에는 모든 것을 lvalues로 출력하기 때문입니다.

+0

그래서 과부하 해결은 T &&를 기본 클래스 변환보다 선호한다고 말하는 것입니까? 이 문제를 피하기 위해 서명을 조정할 수있는 방법이 있습니까? –

+0

@deft_code, 당신이 알아 낸 것으로 가정하면 is_convertible '또는'is_base_of '로 이것을 할 수 있습니다. –

+0

@litb,'is_base_of'도 T가 개인적으로'std :: ostream'을 상속받을 때 참이 아니겠습니까? 나는 왜 누군가가 그렇게할지 모르지만 예제에서'static_cast'가 컴파일되지 않게 할 것입니다. –