Assert that code does NOT compile
Asked Answered
A

7

28

In short:

How to write a test, that checks that my class is not copyable or copy-assignable, but is only moveable and move-assignable?

In general:

How to write a test, that makes sure that a specific code does not compile? Like this:

// Movable, but non-copyable class
struct A
{
  A(const A&) = delete;
  A(A&&) {}
};

void DoCopy()
{
  A a1;
  A a2 = a1;
}

void DoMove()
{
  A a1;
  A a2 = std::move(a1);
}

void main()
{
  // How to define these checks?
  if (COMPILES(DoMove)) std::cout << "Passed" << std::endl;
  if (DOES_NOT_COMPILE(DoCopy)) std::cout << "Passed" << std::endl;
}

I guess something to do with SFINAE, but are there some ready solutions, maybe in boost?

Allpurpose answered 8/5, 2014 at 16:46 Comment(3)
what is the effort of if (DOES_NOT_COMPILE(DoCopy)) std::cout << "Passed" << std::endl; if it is not compiling?Graham
related: How to unit test deliberate compilation errors? at #7282850Ventris
@Graham That is what he is asking...Senega
A
4

A good answer is given at the end of a great article "Diagnosable validity" by Andrzej Krzemieński:

A practical way to check if a given construct fails to compile is to do it from outside C++: prepare a small test program with erroneous construct, compile it, and test if compiler reports compilation failure. This is how “negative” unit tests work with Boost.Build. For an example, see this negative test form Boost.Optional library: optional_test_fail_convert_from_null.cpp. In configuration file it is annotated as compile-fail, meaning that test passes only if compilation fails.

Allpurpose answered 30/5, 2016 at 0:11 Comment(0)
B
27
template<class T>struct sink{typedef void type;};
template<class T>using sink_t=typename sink<T>::type;

template<typename T, typename=void>struct my_test:std::false_type{};
template<typename T>struct my_test<T,
  sink_t<decltype(

put code here. Note that it must "fail early", ie in the signature of a function, not in the body

  )>
>:std::true_type {};

The above generates a test if the "put code here" can be evaluated.

To determine if "put code here" cannot be evaluated, negate the result of the test.

template<class T>using not_t=std::integral_constant<bool, !T::value>;
not_t< my_test< int > >::value

will be true iff "put code here" fails at the substitution stage. (or you can do it more manually, by swapping std::true_type and std::false_type above).

Failing at the substitution stage is different than general failure, and as it has to be an expression you are somewhat limited in what you can do. However, to test if copy is possible, you can do:

template<typename T, typename=void>struct copy_allowed:std::false_type{};
template<typename T>struct copy_allowed<T,
  sink_t<decltype(
    T( std::declval<T const&>() )
  )>
>:std::true_type {};

and move:

template<typename T, typename=void>struct move_allowed:std::false_type{};
template<typename T>struct move_allowed<T,
  sink_t<decltype(
    T( std::declval<T>() )
  )>
>:std::true_type {};

and only move:

template<typename T>struct only_move_allowed:
  std::integral_constant<bool, move_allowed<T>::value && !copy_allowed<T>::value >
{};

The general technique above relies on SFINAE. The base traits class looks like:

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

Here, we take a type T, and a second (anonymous) parameter we default to void. In an industrial strength library, we'd hide this as an implementation detail (the public trait would forward to this kind of private trait.

Then we specialize.

template<typename T>struct whatever<T, /*some type expression*/>:std::true_type{};

the trick is that we make /*some type expression*/ evaluate to the type void if and only if we want our test to pass. If it fails, we can either evaluate to a non-void type, or simply have substitution failure occur.

If and only if it evaluates to void do we get true_type.

The sink_t< some type expression> technique takes any type expression and turns it into void: basically it is a test for substitution failure. sink in graph theory refers to a place where things flow into, and nothing comes out of -- in this case, void is nothing, and the type flows into it.

For the type expression, we use decltype( some non-type expression ), which lets us evaluate it in a "fake" context where we just throw away the result. The non-type expression is now being evaluated only for the purpose of SFINAE.

Note that MSVC 2013 has limited or no support for this particular step. They call it "expression SFINAE". Alternative techniques have to be used.

The non-type expression gets its type evaluated. It isn't actually run, and it does not cause ODR usage of anything. So we can use std::declval<X>() to generate "fake" instances of a type X. We use X& for lvalues, X for rvalues, and X const& for const lvalues.

Bergamo answered 8/5, 2014 at 17:54 Comment(7)
I love how expressive C++ is.Senega
@LightnessRacesinOrbit Yep -- the decision to "instead of adding magic to the language, add support so you can write magic in the language" continues to pay dividends.Bergamo
This is great, but can we package it in something like COMPILES like OP asked for? std::string x; if (COMPILES(foo(x) + bar(1,2,3)){ .. }.Boomerang
Look like this not work for case, when expression which is argument of decltype can't (and shouldn't) compile. Example: coliru.stacked-crooked.com/a/ee9a0acc5b6db902Diderot
@fk0 the sink test must depend on the type T used in my_test for it to work. A test that always fails, regardless of what T is, isn't much of a test.Bergamo
Good explanation, however I think your copy/move example is broken. Additionally, years later (since c++17), we have void_t, so the sink_t is no longer needed. They also have a good explanation an example on cppreference.com: en.cppreference.com/w/cpp/types/void_tTidy
@Tidy replaced to falses with two trues.Bergamo
C
9

You're looking for type traits, defined in <type_traits>, to test whether types have certain properties.

Consciousness answered 8/5, 2014 at 16:50 Comment(6)
Great! And what about the general question? I.e. how to check the arbitrary code?Allpurpose
You just shouldn't have code that doesn't compile. I am making the assumption that what you really want is to test the properties of types, not actually test if code doesn't compile. If code doesn't compile, your compiler will certainly already tell you, right?Consciousness
Well, currently yes, your answer is enough for me. I'm just curious.Allpurpose
I believe it's impossible, by design. If your code doesn't compile, then you can't run it. Thus your check will never be executed. But the good news is that if the code doesn't compile, your compiler will tell you why, so basically the compiler is already doing your check for you. Or something like that.Consciousness
The compiler is the only entity that knows whether or not some block of code can be compiled. (e.g., a compiler that isn't fully standards-compliant won't be able to compile perfectly standard code - and it's the only thing that can possibly know this.) So instead of asking if there is some way of checking if something compiles, see if you can reframe the question into something more workable. For example: using std::is_move_assignable<A>::value && std::is_move_constructible<A>::valueTweeter
You can use some tricks like has_myfunc_func<T> based on sfinae as you do for move constructor with traits. But you have to create those structs in some way.Tedric
M
4

If the goal is to ensure that the code won't compile, you can't have it as part of your test program, since otherwise, your test program won't compile. You have to invoke the compiler on it, and see what the return code is.

Myongmyopia answered 8/5, 2014 at 16:56 Comment(7)
Why not? Failure is an option here; in fact, it is the only option. The test succeeds if the program won't compile. The trick is to ensure that the compilation failed for the right reason.Sacramental
@DavidHammen Maybe I wasn't clear. If such a test is part of your test suite, it can't be embedded in the test program; the test program needs to run a separate process, invoking the compiler, testing the return state, and eventually verifying the error messages (but that can be very difficult, since they vary so much from one compiler to the next).Myongmyopia
I think that the correct approach here, as said before, is to use type traits. This would ensure that the type is doing what it's meant to do and can be tested in a normal way, such as using a unit test framework.Tedric
@GermánDiago The problem is to ensure that something that shouldn't compile doesn't. You can't do that with type traits; you have to compile. Any decent test harness will provide a means for testing external code.Myongmyopia
@James Kanze My understanding is, correct me if I am wrong, that if the trait compiles and is false (is_move_constructible...) the code will not compile if you try to move construct. So in this case you check the behaviour that you want: if you move construct a type, then it will not compile, which is the whole purpose if I understood correctly.Tedric
@GermánDiago Yes. But you don't need the traits for the code not to compile; just use the feature. (Testing that the traits behave correctly as well is a good additional test, of course.) And in order to know that the code doesn't compile, you still have to try and compile it, whether you use the traits or some ad hoc code that shouldn't compile.Myongmyopia
@James Kanze Actually I don't see the point in that if I have a trait that returns false, it means, in practice, that the code won't compile under those circumstances. The title says assert that the code does not compile and the traits check meets the requirements, though, strictly speaking you are not asserting a compilation failure in itself with a trait test. Anyway, and with this I already finish, the question was marked solved for the traits reply above, so it did the right thing for the question :)Tedric
A
4

A good answer is given at the end of a great article "Diagnosable validity" by Andrzej Krzemieński:

A practical way to check if a given construct fails to compile is to do it from outside C++: prepare a small test program with erroneous construct, compile it, and test if compiler reports compilation failure. This is how “negative” unit tests work with Boost.Build. For an example, see this negative test form Boost.Optional library: optional_test_fail_convert_from_null.cpp. In configuration file it is annotated as compile-fail, meaning that test passes only if compilation fails.

Allpurpose answered 30/5, 2016 at 0:11 Comment(0)
P
1

for example this std::is_nothrow_move_assignable<std::string>::value returns true in compile-time.

for more checkers see https://en.cppreference.com/w/cpp/types#Supported_operations

i recommend using it along with static_assert, see https://en.cppreference.com/w/cpp/language/static_assert

now in general i was trying to check if i can call a specific method on some object. this boils down to "assert if this code compiles" and there is a neat and short way to check it.

template<typename T> using canCallPrintYesOn = decltype(::std::declval<T>().printYes());

constexpr bool canCallPrintYesOn_MyType = std::experimental::is_detected<canCallPrintYesOn, MyType>::value;

static_assert(canCallPrintYesOn_MyType, "Should be able to call printYes(void) on this object");

if this fails, you get compile error with above string

Pentad answered 13/9, 2019 at 9:37 Comment(0)
C
0

You might have to structure your code a bit differently to use it, but it sounds like you might be looking for

static_assert ( bool_constexpr , message )

Performs compile-time assertion checking (since C++11): Explanation: bool_constexpr - a constant expression that is contextually convertible to bool; message - string literal that will appear as compiler error if bool_constexpr is false. A static assert declaration may appear at block scope (as a block declaration) and inside a class body (as a member declaration)

Complaint answered 9/5, 2014 at 8:55 Comment(1)
No, that's not what I'm looking for.Allpurpose
G
0

These kinds of tests are usually done at the build system level. The core idea is to let the build system invoke the compiler in some way and expect a failed build result.

For example, if you use CMake, you can create tests by following Expected build-failure tests in CMake. If you use gn, here's an example from the Chromium repo.

Gardenia answered 26/12, 2023 at 21:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.