【问题标题】:Variadic template and inferred return type in concatconcat 中的可变参数模板和推断的返回类型
【发布时间】:2012-07-22 00:31:31
【问题描述】:

这行得通

玩弄 C++11,我尝试构建一个函数,通过将任意对象写入ostringstream 来连接它们。作为这些的辅助函数,我有一个可变参数辅助函数,它将单个项目附加到现有的 ostream (下面的完整粘贴中给出了更多上下文):

template<class Head, class... Tail>
std::ostream& append(std::ostream& out, const Head& head, const Tail&... tail)
{
  return append(out << head, tail...);
}

这失败了

但后来我认为可能有一些对象,当&lt;&lt;-应用于流时,不会返回ostream,而是一些占位符。因此,让流类型也成为模板参数会很酷:

  1 #include <iostream>
  2 #include <sstream>
  3 
  4 template<typename Stream>
  5 Stream& append(Stream& out) {
  6   return out;
  7 }
  8 
  9 template<class Stream, class Head, class... Tail>
 10 auto append(Stream& out, const Head& head, const Tail&... tail)
 11   -> decltype(append(out << head, tail...))  // <<<<< This is the important line!
 12 {
 13   return append(out << head, tail...);
 14 }
 15 
 16 template<class... Args>
 17 std::string concat(const Args&... args) {
 18   std::ostringstream s;
 19   append(s, args...);
 20   return s.str();
 21 }
 22 
 23 int main() {
 24   std::cout << concat("foo ", 3, " bar ", 7) << std::endl;
 25 }

但是g++-4.7.1 会拒绝编译这个。

将签名中Stream 的所有用法改回std::ostream 不会让它变得更好,所以我认为new function declaration syntax 在这里扮演着重要角色——尽管gcc claims 支持它,因为4.4.

错误信息

错误消息相当神秘,并没有告诉我这里发生了什么。但也许你能理解它。

 In instantiation of ‘std::string concat(const Args& ...) [with Args = {char [5], int, char [6], int}; std::string = std::basic_string<char>]’:
24:44:   required from here
19:3: error: no matching function for call to ‘append(std::ostringstream&, const char [5], const int&, const char [6], const int&)’
19:3: note: candidates are:
5:9: note: template<class Stream> Stream& append(Stream&)
5:9: note:   template argument deduction/substitution failed:
19:3: note:   candidate expects 1 argument, 5 provided
10:6: note: template<class Stream, class Head, class ... Tail> decltype (append((out << head), append::tail ...)) append(Stream&, const Head&, const Tail& ...)
10:6: note:   template argument deduction/substitution failed:
 In substitution of ‘template<class Stream, class Head, class ... Tail> decltype (append((out << head), tail ...)) append(Stream&, const Head&, const Tail& ...) [with Stream = std::basic_ostringstream<char>; Head = char [5]; Tail = {int, char [6], int}]’:
19:3:   required from ‘std::string concat(const Args& ...) [with Args = {char [5], int, char [6], int}; std::string = std::basic_string<char>]’
24:44:   required from here
10:6: error: no matching function for call to ‘append(std::basic_ostream<char>&, const int&, const char [6], const int&)’
10:6: note: candidate is:
5:9: note: template<class Stream> Stream& append(Stream&)
5:9: note:   template argument deduction/substitution failed:
10:6: note:   candidate expects 1 argument, 4 provided

问题

所以我的核心问题是:
这段代码是否有充分的理由失败?

我会对标准中的一些引用感兴趣,即我的代码无效,或者对实现中出现的问题有所了解。如果有人应该为此找到一个 gcc 错误,那也将是一个答案。我一直找不到合适的报告。虽然使用std::ostream 仅适用于我当前的应用程序,但实现这项工作的方法也很棒。关于其他编译器如何处理这个问题的输入也很受欢迎,但对于我考虑接受的答案来说还不够。

【问题讨论】:

  • 我发现你添加带有行号的代码以及带有行号但这些数字不匹配的错误很有趣。您确定错误在尾随返回类型中吗?您可以通过使 append 返回 void 来测试这一点(您根本没有使用返回类型,所以没有必要返回任何东西。(注意,我没有看到任何明显的语法错误代码级别,我相信它应该可以编译。你测试过其他编译器吗?)
  • @DavidRodríguez-dribeas:是的,它是尾随返回类型。根据 Jonathan Wakely 的回答,这甚至是有道理的。使用void 返回类型确实使问题消失了,正如您所指出的,这似乎是一个完美的解决方法。因此,我恳请您也提出这个答案,以便我也可以给您一个赞成票。这些答案将很好地相互补充,一个给出原因,另一个给出解决方法。

标签: c++ c++11 g++ type-inference variadic-templates


【解决方案1】:

3.3.2 [basic.scope.pdecl]
-1- 名称的声明点紧接在其完整声明符(第 8 条)之后和其初始化程序(如果有)之前,除非以下说明。

函数声明器包含尾随返回类型,因此函数自己的名称不在其自己的尾随返回类型的范围内。

所以在表达式decltype(append(out &lt;&lt; head, tail...))中唯一的候选函数是非可变参数append(Stream&amp;),当参数包tail不为空时不能使用,所以在调用append时,如果有两个以上的调用,推导总是失败论据。

因此GCC拒绝代码是正确的。

标准委员会成员去年 12 月对此进行了讨论,并将其作为核心问题报告,请参阅 CWG 1433

我现在能想到的唯一解决方法是尝试使用common_type,它适用于一些情况,但可能对其他情况无效:

template<class Stream, class Head, class... Tail>
  auto append(Stream& out, const Head& head, const Tail&... tail)
  -> typename std::common_type<decltype(out << head), decltype(out << tail)...>::type

如果out &lt;&lt; head &lt;&lt; tail 有效但out &lt;&lt; tail 无效,或者任何operator&lt;&lt; 调用返回的内容无法转换为其他operator&lt;&lt; 调用返回的类型,这将失败。

【讨论】:

  • 很好的答案,无论是内容还是演示。谢谢!
  • 在这种特殊情况下的另一种解决方法是让 ADL 在实例化点找到它。我想s 的类型可能会被滥用。类似struct MyADLFinder : std::ostringstream {};
  • 鉴于函数的使用,第三种更简单的解决方法是根本不返回任何内容,并且返回类型为 void
  • @DavidRodríguez-dribeas,是的,这更有意义!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多