C++ 函数 strprint(expr, expr, expr...) 几乎可以工作,但不完全是,为什么?

C++ function strprint(expr, expr, expr...) almost works, but not quite, why?

提问人:jyelon 提问时间:4/8/2023 更新时间:4/8/2023 访问量:77

问:

这是一个小的C++“strprint”函数,似乎大部分工作:

#include <sstream>
#include <iostream>

// send_to_stream: send all arguments to the specified stream.
inline void send_to_stream(std::ostream &os) {}
template <typename ARG, typename... REST>
inline void send_to_stream(std::ostream &os, const ARG &arg, const REST & ... rest) {
    os << arg;
    send_to_stream(os, rest...);
}

// strprint: convert all arguments to a string by sending them to a stringstream.
template <typename... ARGS>
std::string strprint(const ARGS & ... args) {
    std::ostringstream oss;
    send_to_stream(oss, args...);
    return oss.str();
}

int main(int argc, char **argv) {
    std::string s1 = strprint("five:", 5, "\n");
    std::cout << "S1=" << s1 << "." << std::endl;

    // std::string s2 = strprint("five:", 5, std::endl);
    // std::cout << "S2=" << s1 << "." << std::endl;
};

此程序在运行时将产生输出:

five:5
.

这正是我所期望的。但是当我取消注释“s2”行时,我收到编译错误:

foo.cpp: In function ‘int main(int, char**)’:
foo.cpp:25:53: error: too many arguments to function ‘std::string strprint(const ARGS& ...) [with ARGS = {}; std::string = std::__cxx11::basic_string<char>]’
   25 |      std::string s2 = strprint("five:", 5, std::endl);
      |                                                     ^
foo.cpp:15:13: note: declared here
   15 | std::string strprint(const ARGS & ... args) {
      |             ^~~~~~~~

这毫无意义。一个需要无限数量参数的函数的参数太多?它似乎不喜欢通过变量列表传递“std::endl”。 为什么不呢?我错过了什么?

C++ 模板 STL variadic 模板

评论

0赞 PaulMcKenzie 4/8/2023
什么是数据类型?是什么让它成为可以作为论据的东西?std::endl
1赞 HolyBlackCat 4/8/2023
std::endl,作为函数模板,没有类型。所以不能正确推断,最终被推导出为空列表。ARGS...
0赞 463035818_is_not_an_ai 4/8/2023
fwiw 参数的数量不是无限的。这个限制足够高,通常你不会说出来。
0赞 Remy Lebeau 4/8/2023
@jyelon 顺便说一句,您的可变参数模板应该使用转发引用而不是常量左值引用,例如:template <typename ARG, typename... REST> inline void send_to_stream(std::ostream &os, ARG &&arg, REST &&... rest) { os << std::forward<ARG>(arg); send_to_stream(os, std::forward<REST>(rest)...); } template <typename... ARGS> std::string strprint(ARGS && ... args) { ... send_to_stream(oss, std::forward<ARGS>(args)...); ... }

答:

2赞 463035818_is_not_an_ai 4/8/2023 #1

std::endl是一个函数模板

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

诚然,错误消息相当令人困惑。问题是无法推断出 of 的类型,因为您需要先实例化函数:std::endl

std::string s2 = strprint("five:", 5, std::endl<std::ostream::char_type,std::ostream::traits_type>);
std::cout << "S2=" << s1 << "." << std::endl;

现场演示

通常

std::cout << std::endl;

由于 https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt 这里的过载 20 而工作。通常,所有 io 操纵器都是以流为参数的函数。并且 generall 所有仅为输入或输出流定义的 io 操纵器分别是函数模板。

当然,您不想显式提供 和 .正如 Remy Lebeau 所指出的,您可以使用(非捕获)lambda:char_typetraits_type

auto endl = [](std::ostream os&){ os << std::endl; }; 
std::string s2 = strprint("five:", 5, endl);

这将调用重载 18(来自 avove 链接)。

评论

0赞 jyelon 4/8/2023
谢谢!这是有道理的。那么,运算符<<如何接受函数模板作为右侧的参数呢?有什么方法可以我自己做同样的魔术吗?
0赞 463035818_is_not_an_ai 4/8/2023
@jyelon en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt 18
1赞 Remy Lebeau 4/8/2023
实际上,@463035818_is_not_a_number会使用重载 #20 而不是 #18,因为它需要/返回 a 而不是 a 或std::endlbasic_ostream&ios_base&basic_ios&
0赞 Remy Lebeau 4/8/2023
@jyelon有一个重载,它接受匹配 的 签名的 C 样式函数指针。编译器可以推断出要实例化的类型,因为运算符的参数使用被打印到的模板参数。你的ostream::operator<<std::endlstd::endlostreamstrprint()
0赞 Remy Lebeau 4/8/2023
@463035818_is_not_a_number不太详细的解决方案是使用 lambda 而不是显式实例化,例如:demoauto endl = [](std::ostream os&){ os << std::endl; }; std::string s2 = strprint("five:", 5, endl);