Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assert that code does NOT compile

Tags:

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?

like image 767
Mikhail Avatar asked May 08 '14 16:05

Mikhail


People also ask

What does assert 0 mean?

assert(0) or assert(false) is usually used to mark unreachable code, so that in debug mode a diagnostic message is emitted and the program is aborted when the supposedly unreachable is actually reached, which is a clear signal that the program isn't doing what we think it is.

What is the difference between assert () and Static_assert ()?

static_assert is meant to make compilation fail with the specified message, while traditional assert is meant to end the execution of your program.


1 Answers

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::false_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::false_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.

like image 123
Yakk - Adam Nevraumont Avatar answered Oct 20 '22 01:10

Yakk - Adam Nevraumont