16

I have a class that has no default constructor, but the constructor may throw. I was wanting to have a test like:

EXPECT_THROW(MyClass(param), std::runtime_error);

But the compiler, g++, complains that there is no default constructor for MyClass. However, the following...

EXPECT_THROW(MyClass foo(param), std::runtime_error);

...works, and the test passes as expected. Why though won't Googletest accept the temporary object?

class MyClass
{
public:
  MyClass(std::string const& filename);
  //...
};

Interestingly enough, I had refactored my test to not have the filename in a separate variable, and when asked to check I found the following works:

EXPECT_THROW(MyClass("somefilename"), std::runtime_error);

However the following doesn't:

std::string filename("somefilename");
EXPECT_THROW(MyClass(filename), std::runtime_error);
Fraser
  • 74,704
  • 20
  • 238
  • 215
thumper
  • 423
  • 1
  • 4
  • 11

3 Answers3

17

If you're hit with the "most vexing parse", the cure is often uniform initialization:

EXPECT_THROW(MyClass{param}, std::runtime_error);

(assuming your compiler understands C++11).

Bulletmagnet
  • 5,665
  • 2
  • 26
  • 56
  • 5
    Note that if MyClass takes more than 1 parameter, an extra set of parenthesis is needed because EXPECT_THROW is a macro: `EXPECT_THROW((MyClass{param1, param2}), std::runtime_error);` – joesdiner Mar 03 '20 at 16:05
8

When dealing with macros, ultimate tool is analyzing expanded macro:

In your case (and for gtest 1.6):

EXPECT_THROW(MyClass(filename), std::runtime_error);

Expands to:

...
    bool gtest_caught_expected = false; \
    try { \
      if (::testing::internal::AlwaysTrue()) { MyClass(filename); }; \
    } \
    catch (std::runtime_error const&) { \
      gtest_caught_expected = true; \
    } \
    catch (...) { \
      gtest_msg.value = \
          "Expected: " "MyClass(filename)" " throws an exception of type " \
          "std::runtime_error" ".\n  Actual: it throws a different type."; \
      goto gtest_label_testthrow_88; \
    } \
    if (!gtest_caught_expected) { \
      gtest_msg.value = \
          "Expected: " "MyClass(filename)" " throws an exception of type " \
          "std::runtime_error" ".\n  Actual: it throws nothing."; \
      goto gtest_label_testthrow_88; \
    } \
...

As you can see, argument to EXPECT_THROW is not object, but expression to be evaluated further, within GTEST provided valid try/catch block.

So anything you pass to it, must be able to evaluate as expression within nested scope of current scope. In your case:

MyClass(filename)

Is ambiguous, but according to Most vexing parse rule, declaration interpretation is preferred, so you end up with:

MyClass filename

So you are creating variable named filename of class MyClass - and hence error about missing constructor.

This mechanism is not triggered if you use literal string:

MyClass("some string")

Becouse following would be invalid (and there is no ambiguity):

MyClass "some string"
kwesolowski
  • 695
  • 8
  • 18
2

Can you give more information? I constructed an example which works fine with a class that only has a one argument constructor.

#include <iostream>
#include <stdexcept>

#include "gtest/gtest.h"

class m {
    public:
        m(std::string a) {std::cout << "one argument constructor" << std::endl;}
};

int main() {
    EXPECT_THROW(m("hat"), std::runtime_error);
}

Output:

one argument constructor
gtt.cc:12: Failure
Expected: m("hat") throws an exception of type std::runtime_error.
Actual: it throws nothing.

EDIT I do not claim to be an expert on the arcane inner-workings of the C/C++ pre-processor, but I think this has to do with the rules followed when evaluating expressions, in particular, in the land of the pre-processor, parentheses are king. When the pre-processor evaluates MyClass(filename) it first evaluates filename which produces a temporary value which is immediately discarded, it then evaluates MyClass(). Calling MyClass("filename") causes the pre-processor to actually copy the literal string into the expression. One way around this problem is to call EXPECT_THROW((MyClass(filename)), std::runtime_error), i.e. use a set of enclosing parentheses around your statement.

Keith Pinson
  • 7,835
  • 7
  • 61
  • 104
Marty B
  • 243
  • 3
  • 10
  • Ha... isn't that weird. I went to reproduce it, and I found that my refactoring caused it not to happen. However reversing my refactoring and I found the error. I'll add the details above. – thumper Jun 23 '11 at 02:42
  • Added tentative answer in my post – Marty B Jun 24 '11 at 01:05