This works
Toying with C++11, I tried to build a function which concatenates arbitrary objects by writing them to a ostringstream. As a helper function for those, I have a variadic helper function which appends a single item to an existing ostream (More context given in the full paste below):
template<class Head, class... Tail>
std::ostream& append(std::ostream& out, const Head& head, const Tail&... tail)
{
return append(out << head, tail...);
}
This fails
But then I thought that there might be some objects which, when <<-applied to a stream, will not return an ostream but instead some placeholder. So it would be cool to have the stream type a template argument as well:
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 }
But g++-4.7.1 will refuse to compile this.
Changing all uses of Stream in the signature back to std::ostream will not make it any better, so I assume that the new function declaration syntax is playing a major role here – even though gcc claims to support it since 4.4.
Error message
The error message is rather cryptic, and doesn’t tell me what’s going on here. But perhaps you can make any sense of it.
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
Question
So my core question is this:
Is there a good reason for this code to fail?
I’d be interested either in some quote from the standard which says my code is invalid, or some insight as to what’s going wrong here in the implementation. If anyone should find a gcc bug for this, that would be an answer, too. I haven’t been able to find a suitable report. A way to make this work would be great, too, although using std::ostream only works well enough for my current application. Input about how other compilers handle this is appreciated as well, but won’t be enough for an answer I’d consider to accept.
A function declarator includes the trailing return type, so a function’s own name is not in scope in its own trailing return type.
So in the expression
decltype(append(out << head, tail...))the only candidate function is the non-variadicappend(Stream&)which cannot be used when the parameter packtailis not empty, so deduction always fails when callingappendwith more than two arguments.Therefore GCC is correct to reject the code.
This was discussed by the standard committee members last December and reported as a core issue, see CWG 1433.
The only workaround I can think of right now is to try using
common_type, which will work for some cases, but probably fail for others:This would fail if
out << head << tailis valid butout << tailis not, or if any of theoperator<<calls returns something that cannot be converted to the type returned by the otheroperator<<calls.