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?
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.
A good answer is given at the end of a great article "Diagnosable validity" by Andrzej Krzemieński:
You're looking for type traits, defined in
<type_traits>
, to test whether types have certain properties.for example this
std::is_nothrow_move_assignable<std::string>::value
returnstrue
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_assertnow 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.
if this fails, you get compile error with above string
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 )
put code here. Note that it must "fail early", ie in the signature of a function, not in the body
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.
will be true iff "put code here" fails at the substitution stage. (or you can do it more manually, by swapping
std::true_type
andstd::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:
and move:
and only move:
The general technique above relies on SFINAE. The base traits class looks like:
Here, we take a type
T
, and a second (anonymous) parameter we default tovoid
. 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.
the trick is that we make
/*some type expression*/
evaluate to the typevoid
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 gettrue_type
.The
sink_t<
some type expression>
technique takes any type expression and turns it intovoid
: 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 typeX
. We useX&
for lvalues,X
for rvalues, andX const&
forconst
lvalues.