10

I would like to make sure that a static_assert works as it should in a unit test. That is, if I have a

class MyClass {static_assert(my_type_trait<T>::value, "error"); };

then in the unit test MyClass<TypeWithTrait> myClass; should 'pass' and MyClass<TypeWithoutTrait> myClass; should 'fail'.

Is it possible to do something like this?

David Doria
  • 9,873
  • 17
  • 85
  • 147
  • Yes it is, have you tried it? – 101010 Feb 08 '16 at 20:45
  • @101010 Yes, I just get a compiler error. – David Doria Feb 08 '16 at 20:46
  • @DavidDoria: so? That's exactly what static asserts are for. Test passed if that was expected, failed if it wasn't. – Mat Feb 08 '16 at 20:48
  • 7
    @Mat But I can't build the project to get to a point where I can run the tests. At this point it's not a "unit test" but rather a "I can manually try it and see, but I can't automatically do that every time test suite is run to check for regressions." Do you see what I mean? – David Doria Feb 08 '16 at 20:50
  • You're supposed to test if it builds, not include it as a normal source file in your project. (If your IDE/framework can't handle that, make scripts for it.) – Mat Feb 08 '16 at 20:52
  • `static` goes for compile time. Meaning that if your test fails you'll get a compile error. – 101010 Feb 08 '16 at 20:53
  • @101010 Then the question is how to perform a compile-time test with GTest. – David Doria Feb 08 '16 at 21:02
  • @Mat I guess that's what I'm asking how to do with GTest. – David Doria Feb 08 '16 at 21:03
  • 4
    This is one of my biggest complaints with typical unit test libraries. Making sure you fail is as important as making sure you pass. I believe LLVM's lit (http://llvm.org/docs/CommandGuide/lit.html) has this capability. Though to be completely transparent I've never used it (I may in the future). In the past I've written bash-shell script code like this to solve this problem: https://github.com/llvm-mirror/libcxx/blob/master/test/testit#L83-L95 This solution has been disliked, mainly because of its inability to run tests in parallel, and inability to detect tests failing for the wrong reasons. – Howard Hinnant Feb 09 '16 at 03:50
  • Possible duplicate of [How to write runnable tests of static\_assert?](http://stackoverflow.com/questions/17408824/how-to-write-runnable-tests-of-static-assert) – Mike Kinghan Feb 09 '16 at 15:43
  • @HowardHinnant Can you possibly re-link that? It seems to have been lost to the ravages of time due to the link using the master branch. Thank you. – Alex Huszagh Jan 27 '18 at 23:57
  • It appears `testit` is no longer part of libcxx. – Howard Hinnant Jan 28 '18 at 00:31
  • This is not an answer, but a possible workaround is to make the condition inside the `static_assert` be a call to a `constexpr` function that is also accessible by the test suite. It may even be part of the API so that users of the class may verify the static "precondition" themselves. – Emile Cormier May 05 '22 at 22:11

2 Answers2

5

If you want to check that something fails to compile, you'll have to test that external to the code. Just write a simple file like:

#include "MyClass.h"

int main() { 
    MyClass<%T%> m;
}

And write a unit test that compiles that file with different values of %T%. Verify that the compilation either succeeds as expected, or fails with something about static_assert in the failure text as expected.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

Barry's suggestion is one possibility, but if you have many things you want to test, you need to create many small files. What's more, those files could fail to compile for other reasons than what you expect, giving you a false sense your test passed.

An alternative is that instead of using static_assert, you use some kind of SFINAE to detect whether or not something works. For traits classes this is a bit tricky, but you can do this:

template <class T>
using void_t = void;

template <class T>
struct foo;

template <>
struct foo <double> {};

template <class T, class = void>
struct has_foo_trait : std::false_type {};

template <class T>
struct has_foo_trait<T, void_t<decltype(foo<T>{})>> : std::true_type {};

int main(int, char**) {
  std::cerr << has_foo_trait<int>::value;
  std::cerr << has_foo_trait<double>::value;
  return 0;
}

This prints out 01. So now, instead of getting a hard failure from static_asserting directly, you can compute the value of the trait presence at compile time, and then static_assert that you get the value you expect.

Note that the reason that traits classes are tricky is because the trait is declared as a general template, just not defined. So doing the "usual" metaprogramming thing of using the type directly inside the void_t does not work. In order to trigger a soft SFINAE error in the true branch of has_foo_trait, I actually had to default construct an instance of the traits class. If you write your traits classes so they're not default constructible, this won't work. But in general you wouldn't write them that way. Would be curious to see if there's a better way to do it

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
  • 1
    So I guess one case I'm trying to check is that someone didn't remove the static_assert from a class. So if I understood what you're saying correctly, this would help in the case that I was just trying to check if a static_assert WOULD fail, but if I'm trying to check if a static_assert is actually present and failing where it should I don't think this helps. – David Doria Feb 08 '16 at 21:15