14

I found somewhere the following idiom for reading a file into a string:

std::ifstream file("path/to/some/file.ext");
std::string contents(
    std::istreambuf_iterator<char>(file),
    (std::istreambuf_iterator<char>())
);

Which works just fine as it is. However, if I remove the parentheses around the second iterator argument, that is:

std::string contents(
    std::istreambuf_iterator<char>(file),
    std::istreambuf_iterator<char>()
);

As soon as I try to call any method on the string object, for example:

const char *buffer = contents.c_str();

I get a compile error of the form:

error: request for member 'c_str' in 'contents', which is of non-class type 'std::string(std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> > (*)()) {aka std::basic_string<char>(std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> > (*)())}'

Also if I try to assign that string to another:

std::string contents2 = contents;

I get an error of the form:

error: conversion from 'std::string(std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> > (*)()) {aka std::basic_string<char>(std::istreambuf_iterator<char, std::char_traits<char> >, std::istreambuf_iterator<char, std::char_traits<char> > (*)())}' to non-scalar type 'std::string {aka std::basic_string<char>}' requested

Why is this? I can see no reason for those parentheses being needed, much less affect the type definition of the contents variable. I am using g++ 4.8.2.

xperroni
  • 2,606
  • 1
  • 23
  • 29
  • So many Most Vexing Parse questions here, but [this one](http://stackoverflow.com/questions/5926103/most-vexing-parsec?rq=1) is pretty similar. – chris Aug 27 '14 at 01:07
  • 3
    That's great as long as you know what Most Vexing Parse is. If you don't (like I didn't), it's difficult to even know what to look for. Which is why I decided to ask this question. – xperroni Aug 28 '14 at 02:39

1 Answers1

22

That is an example of Most Vexing Parse:

std::string contents(
    std::istreambuf_iterator<char>(file),
    std::istreambuf_iterator<char>()
);

The statement is parsed as the declaration of a function named contents that takes two parameters. One parameter that is a variable named file of type std::istreambuf_iterator, and a second one that is a function that takes no arguments and returns an std::istreambuf_iterator. Specification of function parameter names are optional.

Wrapping at least one of the expressions in parenthesis causes it to be parsed as a variable definition.

C++11 solves this problem with its provision of uniform-initialization:

std::string contents{std::istreambuf_iterator<char>{file}, {}};
David G
  • 94,763
  • 41
  • 167
  • 253
  • What's `{}` in `std::string contents{std::istreambuf_iterator{file}, {}};`? Could you please explain that in more detail for me? – John Aug 26 '22 at 07:11
  • @John It's a quick way of initializing a `std::istreambuf_iterator` with the default-constructor without having to write out the whole thing. It's called list-initialization. If you had a function `void foo(int x);` you can call it like `foo({});` and it'll just make a `int` with the value `0`. So it does the same as the first code sample. – David G Aug 29 '22 at 14:25
  • Thank you for the clarification. The list-initialization could deduce the suitable type? – John Aug 30 '22 at 02:19
  • @John It doesn't deduce anything. The complier sees the type you're trying to initialize and uses the list-initializer to construct the object and call the appropriate constructor. – David G Aug 30 '22 at 02:40