【问题标题】:Cannot have typeof(std::endl) as template parameter?不能有 typeof(std::endl) 作为模板参数?
【发布时间】:2012-04-18 10:38:03
【问题描述】:

所以,我试图写一个这样的函数:

void append_to_stream(std::ostream &stream)
{ }

template <typename T, typename... Args>
void append_to_stream(std::ostream &stream, T first, Args&&... rest)
{
  stream << first;
  append_to_stream(stream, rest...);
}

然后这样称呼它:

append_to_stream(stream, 
                 std::endl,
                 std::endl);

但这不起作用。我收到一个错误,提示函数“参数过多”。我已经把它缩小到我知道std::endl 是有罪的——可能是因为它是一个函数。我设法通过声明一个名为endl 的结构并为其定义&lt;&lt;operator 来“解决”这个问题,以便它简单地调用std::endl。这有效,但感觉不是特别好。不能接受 std::endl 作为模板参数吗?该功能适用​​于其他类型。

编辑:这是错误:

src/log/sinks/file_sink.cpp:62:21: error: too many arguments to function ‘void log::sinks::append_to_stream(std::string&, Args&& ...) [with Args = {}, std::string = std::basic_string<char>]’

更新

试图让编译器推断出正确的模板参数@MooingDuck 建议可以使用以下形式的函数:

  template<class e, class t, class a> 
  basic_ostream<e,t>&(*)(basic_ostream<e,t>&os) get_endl(basic_string<e,t,a>& s) 
  {
return std::endl<e,t>;
  }

但是,这不会编译。

错误:

src/log/sinks/file_sink.cpp:42:28: error: expected unqualified-id before ‘)’ token
src/log/sinks/file_sink.cpp:42:53: error: expected initializer before ‘get_endl’

任何想法为什么?为了编译这个,我添加了using namespace std;

【问题讨论】:

  • 你用的是什么编译器? IIRC 一些编译器在一段时间内遇到了这样的递归问题。
  • std::endl 不是函数,而是模板。
  • 我很困惑您的错误消息显示您的流是std::string?这和你说的不符……
  • @Robᵩ:编译器指的是endl 的可能特化集,它们形成了一组重载函数。由于函数参数类型是通用的,因此其中任何一个都会匹配,并且编译器无法从中选择。如果参数具有特定类型(如ostream&amp;(*)(ostream&amp;)),则编译器可以选择具有该类型的特化(endl&lt;char, char_traits&lt;char&gt;&gt;);这就是ostream &lt;&lt; endl 有效的原因。
  • @MooingDuck 这是一个泄漏抽象的例子。我试图将示例简化为基本要素 - 我错误地将 std::string 参数留在其中。真正的函数由创建 fstream 的函数包装。

标签: c++ c++11 variadic-templates


【解决方案1】:

std::endl 是模板,而不是函数,编译器无法解析使用哪个endl

试试:

append_to_stream(std::cout,
             std::endl<char, std::char_traits<char>>,
             std::endl<char, std::char_traits<char>>);

或者,MooingDuck 的解决方案(已更正):

template<class e, class t, class a> //string version
std::basic_ostream<e, t>& (*get_endl(const std::basic_string<e, t, a>&))
    (std::basic_ostream<e, t>& )
{ return std::endl<e,t>; } 

template<class e, class t> //stream version
std::basic_ostream<e, t>& (*get_endl(const std::basic_ostream<e, t>&))
    (std::basic_ostream<e, t>& )
{ return std::endl<e,t>; }

int main () {
  std::ostream& stream = std::cout;
  append_to_stream(stream,
                 get_endl(stream),
                 get_endl(stream));
}

这里是 get_endl 解决方案,由 C++11 decltype 功能简化:

template<class e, class t, class a> //string version
auto get_endl(const std::basic_string<e, t, a>&)
  -> decltype(&std::endl<e,t>)
{ return std::endl<e,t>; }

template<class e, class t> //stream version
auto get_endl(const std::basic_ostream<e,t>&)
  -> decltype(&std::endl<e,t>)
{ return std::endl<e,t>; }

int main () {
  std::ostream& stream = std::cout;
  append_to_stream(stream,
                 get_endl(stream),
                 get_endl(stream));
}

【讨论】:

  • 是的,这很好用,谢谢。不可能 typedef 模板函数,对吧? ://
  • @Max:你也可以考虑一个函数。我认为像“template&lt;class e, class t&gt; basic_ostream&lt;e,t&gt;&amp;(*)(basic_ostream&lt;e,t&gt;&amp;os) get_endl(basic_ostream&lt;e,t&gt;&amp; s) {return std::endl&lt;e,t&gt;;}”这样的东西应该这样做。
  • @MooingDuck 这是个好主意,我猜您使用流来推断 std::endl 的模板参数?问题是我要使用的函数是 append_to_file(std::string, ...) 这意味着在实例化时我没有方便的流对象:/。如果我误解了你,请告诉我!
  • @Max - 查看最新编辑以获取get_endl 解决方案的可读版本。
  • @Max: auto 表示返回类型在右侧而不是左侧(因此我们可以在参数上使用decltype)。它显然被称为“尾随返回类型”
【解决方案2】:

通过强制转换解决重载比指定模板参数或定义全新模板(!)容易得多。

typedef std::ostream & (&omanip_t)( std::ostream & );

append_to_stream(stream, 
                 static_cast< omanip_t >( std::endl ),
                 static_cast< omanip_t >( std::endl ) );

这适用于所有操纵器,而某些操纵器可以采用不同的模板,例如,如果用户提供。

此外,您应该通过完美转发或 const 引用传递 T first。先转发,再传值,意义不大。另外,如果不调用std::forward,右值参数将按值传递……只要遵循成语,就会是

template <typename T, typename... Args>
void append_to_stream(std::ostream &stream, T &&first, Args&&... rest)
{
  stream << std::forward< T >( first );
  append_to_stream(stream, std::forward< Args >( rest ) ... );
}

http://ideone.com/cw6Mc

【讨论】:

  • 似乎 std::forward(rest...) 无法编译。 GCC 说:no matching call to std::forward()。我将此解释为意味着基本情况不起作用,其中 rest... 是空的。这得到了以下事实的支持:如果我将基本情况更改为接受最后一个参数,它编译得很好。也就是说,最好是使用 rvalue 和 std::forward 还是普通的 const ref 传递?有什么区别? :)
  • @Max Yikes,我的转发习惯用语弄错了!两个包都应该由一个 ... 扩展,但我无法直接说明为什么这种方式不起作用。见更新。不同之处在于const &amp; 指定您不会修改参数,因此无论如何在这种情况下它可能会更好。请注意,forward 不依赖于右值引用,如果你给它一个普通的局部变量名,它将通过普通的可修改左值 ref 传递。
猜你喜欢
  • 2014-01-19
  • 2021-07-18
  • 1970-01-01
  • 1970-01-01
  • 2014-01-03
  • 1970-01-01
  • 2019-01-23
  • 2012-02-21
  • 2013-06-14
相关资源
最近更新 更多