2

I've not seen this specific oddity of C++ before and it's causing me a bit of confusion.

I have the following class:

class KeyValuesParser
{
public:
    explicit KeyValuesParser(const QByteArray &input);
    QJsonDocument toJsonDocument(QString* errorString = nullptr);

    // ...
};

And I'm trying to use it like so in a Qt unit test:

const char* testData = "...";
KeyValuesParser parser(QByteArray(testData));
QJsonDocument doc = parser.toJsonDocument();

This gives the following compile error:

Member reference base type 'KeyValuesParser(QByteArray)' is not a structure or union.

However, if I create the byte array on the stack and then pass it in, instead of passing in a temporary, everything compiles fine:

const char* testData = "...";
QByteArray testByteArray(testData)
KeyValuesParser parser(testByteArray);
QJsonDocument doc = parser.toJsonDocument();

I thought this might have been some weird black magic that required the explicit keyword (which is why I added it), but that didn't make any difference. Can anyone explain what's going on here?

EDIT: I've been referred to another question as being a duplicate and I think it almost is, but there's some most-vexing-parse confusion on this question that I think merits the extra discussion.

NoodleCollie
  • 855
  • 7
  • 24
  • Possible duplicate of [Passing temporary object as a reference to an abstract to a constructor](http://stackoverflow.com/questions/34493077/passing-temporary-object-as-a-reference-to-an-abstract-to-a-constructor) – Ken Y-N Dec 01 '16 at 07:46
  • @KenY-N I also feel that there must be a duplicate, but your suggested duplicate isn’t the one. In the suggested duplicate, the nested argument is a value, not a name, which prevents Most Vexing Parse from happening. Most Vexing Parse is the immediate problem here though. – Jonas Schäfer Dec 01 '16 at 08:31
  • Without having been told otherwise, I would have thought that the ```QByteArray(testData)``` argument would have been sufficient to exclude the statement being an MVP of a function declaration, as it's an actual object and not a type name. I didn't think you could _declare_ a function by passing a temporary value to an invisible argument? – NoodleCollie Dec 03 '16 at 00:13

3 Answers3

1

NB. This is addition to Jonas' answer near me.

What are you trying to do is to bind rvalue to lvalue reference, this is not allowed in C++ standard. In your second example - you correctly pass lvalue via reference, that's why it's working.

Binding rvalue to lvalue reference is Visual C++ extension, g++ cannot do it at all, clang can do it with -fms-extensions

Starl1ght
  • 4,422
  • 1
  • 21
  • 49
  • I came back to this file later and that's what it was - the constructor param was stored in the class as a `const QByteArray&`, so passing a temporary didn't work. I think it was the error that confused me, as I would have expected it to pick out that particular member initialisation, rather than saying the whole object wasn't appropriate. – NoodleCollie Dec 01 '16 at 21:22
0

The line

KeyValuesParser parser(QByteArray(testData));

is not an object declaration. It’s a function declaration of a function returning a KeyValuesParser and taking a QByteArray argument.

This is called Most Vexing Parse (link goes to wikipedia, you’ll find plenty on StackOverflow too). To keep it short, when in doubt, the C++ standard prefers a function declaration, because otherwise it would be hard to declare functions at all.

Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
0

As mentioned, this is a "most vexing parse" problem. c++11 brought us two solutions: auto and brace initialisation:

// use auto to turn the expression unambiguously into an rvalue
// without having to mention the class name twice
int main()
{
  const char* testData = "...";
  auto parser = KeyValuesParser(QByteArray(testData));
  auto doc = QJsonDocument(parser.toJsonDocument());
}


// or use brace initialisation to avoid the parse ambiguity 
//    
int main2()
{
  const char* testData = "...";
  KeyValuesParser parser{ QByteArray(testData) };
  QJsonDocument doc{ parser.toJsonDocument() };
}

// another solution:

const char* testData = "...";
auto doc = QJsonDocument(KeyValuesParser(QByteArray(testData)).toJsonDocument());

// yet another

const char* testData = "...";
auto doc = QJsonDocument { KeyValuesParser { QByteArray(testData) }.toJsonDocument() };
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142