2011-05-13 29 views
3

我有一个可变参数函数,我想在第一个参数类型上重载。在可变参数模板函数中的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

为什么你需要最后一个模板专业化?如果没有那个问题,问题就会消失。 – 2011-05-13 20:36:22

+0

@Diego,如果ostream对象不是由调用者提供的,最后一个专门化('write(args)')将'std :: cout'放置为第一个参数。 – 2011-05-13 20:41:06

回答

5

这是因为ostringstream需要一个基地转化为ostream,当你把它传递给其他的模板,而当你把它传递给转发到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,因为我不能用尖括号一定程度的应对。所以我喜欢用超载

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

这样,如果你不是左值的ostream其他的东西把它作为第一个参数,它会调用write_dispatch,这将改变该呼叫转变的ostream这样一个左值,使您的其他write模板可以继续。

最后一点,你应该说out << std::forward<Head>(head),否则你以前的递归步骤中使用std::forward的所有工作都是徒劳的,因为最后你会输出所有东西作为左值。

+0

所以你说重载决议更喜欢T &&基础类转换?有没有办法可以调整签名以避免这种情况? – 2011-05-13 20:38:56

+0

@deft_code,正如我假设你发现的那样,你可以用'is_convertible '或'is_base_of '来做到这一点。 – 2011-05-13 20:39:55

+0

@litb,当T私下继承'std :: ostream'时,'is_base_of'也不会成立?我不知道为什么有人会这样做,但会导致示例中的static_cast不能编译。 – 2011-05-13 22:54:21