10

Assume a template class where we assert at compile time that the integer template argument must be greater zero:

template<int N>
class A
{
public:

    A() {
        static_assert(N > 0, "N needs to be greater 0.");
    }

};

Is it possible to create a googletest unit test that compiles, but reports the error at runtime? For example:

TEST(TestA, ConstructionNotAllowedWithZero)
{
    ASSERT_DOES_NOT_COMPILE( 
        {
            A< 0 > a;
        }
    );
}
  • I think a possible solution may not directly be linked to your unit testing framework. Did you see [How to expect a static_assert failure and deal with it using Boost.Test framework?](http://stackoverflow.com/questions/17408824/how-to-write-runnable-tests-of-static-assert) or [How to write runnable tests of static_assert?](http://stackoverflow.com/questions/17408824/how-to-write-runnable-tests-of-static-assert) – Florian Jul 24 '15 at 11:01
  • Thanks, but those links also do not provide a satisfactory answer. – Dimitri Schachmann Jul 28 '15 at 08:13
  • Just a random stupid idea: `#define static_assert assert` at the top of the test file. Ugly but maybe it's what you want. – Lærne Oct 15 '15 at 07:36

1 Answers1

7

There is a way, but sadly it's probably not the way you want.

My first thought was to try to get SFINAE to discount an overload by expanding an invalid lambda in an unevaluated context. Unhappily (in your case), this is specifically disallowed...

#define CODE { \
 utter garbage \
}
struct test
{
    template<class T>
    static std::false_type try_compile(...) { return{}; }
    template<class T>
    static auto try_compile(int)
    -> decltype([]() CODE, void(), std::true_type());
    { return {}; }
};
struct tag {};
using does_compile = decltype(test::try_compile<tag>(0));

output:

./maybe_compile.cpp:88:17: error: lambda expression in an unevaluated operand
    -> decltype([]() CODE, void(), std::true_type());

So it was back to the drawing board and a good old system call to call out to the compiler...

#include <iostream>
#include <string>
#include <cstdlib>
#include <fstream>
#include <sstream>

struct temp_file {
    temp_file()
    : filename(std::tmpnam(nullptr))
    {}

    ~temp_file() {
        std::remove(filename.c_str());
    }

    std::string filename;
};

bool compiles(const std::string code, std::ostream& reasons)
{
    using namespace std::string_literals;

    temp_file capture_file;
    temp_file cpp_file;

    std::ofstream of(cpp_file.filename);
    std::copy(std::begin(code), std::end(code), std::ostream_iterator<char>(of));
    of.flush();
    of.close();
    const auto cmd_line = "c++ -x c++ -o /dev/null "s + cpp_file.filename + " 2> " + capture_file.filename;
    auto val = system(cmd_line.c_str());

    std::ifstream ifs(capture_file.filename);
    reasons << ifs.rdbuf();
    ifs.close();

    return val == 0;
}

auto main() -> int
{
    std::stringstream reasons1;
    const auto code1 =
R"code(
    #include <iostream>
    int main() {
        return 0;
    }
)code";
    std::cout << "compiles: " << compiles(code1, reasons1) << std::endl;

    std::stringstream reasons2;
    const auto code2 =
R"code(
    #include <iostream>
    int main() {
        FOO!!!!XC@£$%^&*()VBNMYGHH
        return 0;
    }
)code";
    std::cout << "compiles: " << compiles(code2, reasons2) << std::endl;
    std::cout << "\nAnd here's why...\n";
    std::cout << reasons2.str() << std::endl;

    return 0;
}

which in my case gives the following example output:

compiles: 1
compiles: 0

And here's why...
/var/tmp/tmp.3.2dADZ7:4:9: error: use of undeclared identifier 'FOO'
        FOO!!!!XC@£$%^&*()VBNMYGHH
        ^
/var/tmp/tmp.3.2dADZ7:4:19: error: non-ASCII characters are not allowed outside of literals and identifiers
        FOO!!!!XC@£$%^&*()VBNMYGHH
                  ^
2 errors generated.

of course you can add all the necessary macros around the call to compiles() in order to GTESTify it. You will of course have to set command line options on the c-compiler invocation to set the correct paths and defines.

m.s.
  • 16,063
  • 7
  • 53
  • 88
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Thank you for you effort. Nice idea to use a system call to the compiler. :D But you are right: It's not what I want. I begin to doubt, that it's even possible. – Dimitri Schachmann Jul 28 '15 at 08:14
  • 1
    It's not possible because there's no way to evaluate code in an unevaluated context. (You see the dichotomy? ) – Richard Hodges Jul 28 '15 at 11:51
  • nice answer, only a minor nitpick: Your line of reasoning can be a bit misleading, because the problem in the question is not the lambda, but that the constructor has to be called to trigger the static_assert and that doesnt play well with SFINAE. I had to convince myself (https://godbolt.org/z/aVaxrg) and I think it isnt possible to use a trait to detect that assert – 463035818_is_not_an_ai Jul 08 '20 at 11:10