Reputation: 408
In unit tests I want to check, that some expressions can't be compiled. For example, if I'm writing non copyable class, I want to check that copy constructor can't be called (so expression, which calls copy constructor, should not be compiled). Currently I'm using google test, which have no such possibility.
It's known, that this can be made with using of SFINAE. Basic idea is, that expression, which should be tested, should be passed to decltype() as argument. And some part of expression must be a template (without using of template argument compiler immediately points to error). Now, if expression can be compiled, decltype() can determine type of the expression, and particular template will be selected (in which arguments decltype is written). If expression can't be compiled, then other template will be selected. Later, in runtime, we can analyze the result...
The main disadvantage of this method, is that part of expression should be template argument, this leads to bad code readability. The question I want to ask: where is the method to write whole expression as is, without splitting it to template parameter and expression itself? And another question, there is possibility to avoid using of std::declval?
For example, currently I should write:
COMPILE_TEST(..., T, noncopyable, T(std::declval<T>()));
If the template argument was not necessary, I could then write:
COMPILE_TEST(..., noncopyable(std::declval<noncopyable>()));
And in ideal case I want write something like, this:
COMPILE_TEST(..., { noncopyable t; noncopyable t2(t); });
But I guess, this is completely impossible.
Full example (https://coliru.stacked-crooked.com/a/ecbc42e7596bc4dc):
#include <stdio.h>
#include <utility>
#include <vector>
template <typename...> using _can_compile = void;
struct cant_compile { constexpr static bool value = false; };
#if COMPILE_TEST_ASSERTS
#define CAN_COMPILE_ASSERT(val, name, param, decl, ...) \
static_assert(val, "this shoul'd not be compiled (" #name "): " __VA_ARGS__ " [with " #param "=" #decl "]");
#else
#define CAN_COMPILE_ASSERT(name, param, decl, ...) static_assert(true, "")
#endif
#define COMPILE_TEST(name, param, decl, ...) \
template <typename T, typename = void> struct can_compile_##name : public cant_compile {}; \
template <typename T> struct can_compile_##name<T, _can_compile<decltype(__VA_ARGS__)>> { constexpr static bool value = true; }; \
CAN_COMPILE_ASSERT(!can_compile_##name<decl>::value, name, param, decl, #__VA_ARGS__); \
constexpr bool name = can_compile_##name<decl>::value;
struct noncopyable_good
{
noncopyable_good() {}
noncopyable_good(const noncopyable_good&) = delete;
};
struct noncopyable_bad
{
noncopyable_bad() {}
// noncopyable_bad(const noncopyable_bad&) = delete;
};
COMPILE_TEST(good, T, noncopyable_good, T(std::declval<T>()));
COMPILE_TEST(bad, T, noncopyable_bad, T(std::declval<T>()));
int main()
{
printf("noncopyable_good can%s be copied\n", good ? "" : "'t");
printf("noncopyable_bad can%s be copied\n", bad ? "" : "'t");
return 0;
}
Similar questions:
Upvotes: 3
Views: 344
Reputation: 119877
Here's a very slight modification of your code that eliminates the extra parameter.
template <class X, class Y>
auto my_declval() -> Y;
#define MY_DECLVAL(...) my_declval<T, __VA_ARGS__>()
template <typename...> using _can_compile = void;
struct cant_compile { constexpr static bool value = false; };
#if COMPILE_TEST_ASSERTS
#define CAN_COMPILE_ASSERT(val, name, decl) \
static_assert(val, "this shoul'd not be compiled (" #name "): " #decl );
#else
#define CAN_COMPILE_ASSERT(name, decl, ...) static_assert(true, "")
#endif
#define COMPILE_TEST(name, ...) \
template <typename T, typename = void> struct can_compile_##name : public cant_compile {}; \
template <typename T> struct can_compile_##name<T, _can_compile<decltype(__VA_ARGS__)>> { constexpr static bool value = true; }; \
CAN_COMPILE_ASSERT(can_compile_##name<void>::value, name, #__VA_ARGS__); \
constexpr bool name = can_compile_##name<void>::value;
struct noncopyable_good
{
noncopyable_good() {}
noncopyable_good(const noncopyable_good&) = delete;
};
struct noncopyable_bad
{
noncopyable_bad() {}
// noncopyable_bad(const noncopyable_bad&) = delete;
};
COMPILE_TEST(good, noncopyable_good(MY_DECLVAL(noncopyable_good)));
COMPILE_TEST(bad, noncopyable_bad(MY_DECLVAL(noncopyable_bad)));
The idea is that the expression you need to check has to depend on a template parameter, but the template parameter need not be connected to the type you need to check. So MY_DECLVAL is made to vacuously depend on some dummy parameter T
, and the actual argument passed is void
(could be any type).
(I removed the negation in CAN_COMPILE_ASSERT
invocation, because I think it's there by mistake, and simplified its definition).
You need to have at least one MY_DECLVAL in the expression you want to check.
Upvotes: 1