SFINAE to assert() that code DOES NOT compile
The following macro lets you rewrite a SFINAE-unfriendly expression such as [](auto&&x) { return x+1; }
in a SFINAE-friendly way.
#define RETURNS(...)\
noexcept(noexcept(__VA_ARGS__))\
->decltype(__VA_ARGS__)\
{ return __VA_ARGS__;}
So that lets you rewrite the above lambda expression like this:
[](auto&&x) RETURNS( x+1 )
or, another example:
struct { template<class X> auto operator()(X&&x) RETURNS(x+1) };
and it is SFINAE friendly. RETURNS
isn't actually required, but it makes much of the code so much cleaner. There is a c++20 proposal to replace RETURNS
with =>
by SO's own @barry.
Next we need to be able to test if a function object can be called.
namespace details {
template<class, class, class...>
struct can_invoke:std::false_type{};
template<class F, class...Args>
struct can_invoke<
F,
std::void_t<std::result_of_t<F&&(Args&&...)>>,
Args...
>:
std::true_type
{};
}
template<class F, class...Args>
using can_invoke=details::can_invoke<F,void,Args...>;
we are almost there. (This is the core of the technique; I sometimes use can_apply
that takes template<class...>class Z
instead of class F
here.) c++17 has a similar trait; it can be used instead.
test_invoke
takes callable and returns a callable tester. A callable tester takes arguments, and returns true or false types based on "could the original callable be called with these arguments".
template<class F>
constexpr auto test_invoke(F&&){
return [](auto&&...args) RETURNS( can_invoke< F, decltype(args)... >{} );
}
and here we are. test_invoke
can be skipped if you are willing to work with pure types, but working with values can eliminate some bugs.
auto myclass_ctor=[](auto&&...args)RETURNS(myclass_t(decltype(args)(args)...));
myclass_ctor
is a callable object that represents constructing myclass_t
.
static_assert(!test_invoke(myclass_ctor)("Hello") );
or
template<class C>
auto ctor=[](auto&&...args)RETURNS(C(decltype(args)(args)...));
static_assert(!test_invoke(ctor<myclass_t>)("Hello") );
this requires constexpr lambda, a c++17 feature but an early one. It can be done without it but it gets ugly. Plus move ctor requirement of elision is annoying to work around in c++14.
To translate to c++14, replace every lambda with a manual function object with appropriate constexpr special member functions. RETURNS
applies to operator()
just as well, as demonstrated above.
To get around elision move ctor requrement, RETURNS(void( blah ))
.
Apologies for any tyops; I am on phone.
Building on @Yakk's answer, which I find amazing. We can never hope to
static_assert(!DOES_NOT_COMPILE(myclass_t(help)));
because there must be a type dependency to delay the error, and that's what Yakk is doing. Using another macro, together with default lambda capture:
STATIC_ASSERT_NOT_COMPILES(myclass_t(MK_DEP(help)));
MAKE_DEP
is a templated function object, which is injected by the macro to provide the required dependency. Example use:
void foo(){
std::string s;
const std::string cs;
STATIC_ASSERT_NOT_COMPILES(cs=MK_DEP(s));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(cs).clear());
// This fires, because s can be cleared:
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s).clear()); // Fails to compile, OK!
class C{}; // just an example class
C c;
STATIC_ASSERT_NOT_COMPILES(c=MK_DEP(7));
STATIC_ASSERT_NOT_COMPILES(7=MK_DEP(c));
STATIC_ASSERT_NOT_COMPILES(baz(foo(MK_DEP(7)=c)));
STATIC_ASSERT_NOT_COMPILES(MK_DEP(false)=1);
// What about constructing C from string?
STATIC_ASSERT_NOT_COMPILES(C(MK_DEP(std::string{})));
// assert fires: can add strings: OK!
//STATIC_ASSERT_NOT_COMPILES(MK_DEP(s)+cs+std::string());
// Too many arguments to MK_DEP is forced to give hard error: Fails to compile, OK!
// STATIC_ASSERT_NOT_COMPILES(MK_DEP(1,2,3)+1);
// Forgetting to add MK_DEP also gives a *hard* error. Fails to compile. OK!
// STATIC_ASSERT_NOT_COMPILES(7=c);
}
Implementation, relying on Yakk's test_invoke
and RETURNS
. Feedback welcome!
namespace details{
struct make_depend{
template<typename T> static constexpr const bool false_t = false;
template<typename T>
auto operator()(T&& arg) RETURNS(arg) ;
// Try to protect against wrong use: zero or many arguments:
template<typename T, typename... T2>
auto operator()(T&& arg, T2... too_many_arguments) {
static_assert(false_t<T>, "Too many arguments given to MK_DEP"); } ;
template<typename T=int>
auto operator()() { static_assert(false_t<T>, "Too few arguments given to MK_DEP"); } ;
};
}
#define STATIC_ASSERT_NOT_COMPILES(...)\
static_assert(!test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))\
(details::make_depend{}))
Alternatively, a somewhat less wrapped approach:
#define CHECK_COMPILES(...)\
test_invoke([&](auto MK_DEP)RETURNS(__VA_ARGS__))(details::make_depend{})
static_assert(CHECK_COMPILES(cs=MK_DEP(s)));
Or even just the basic idea:
static_assert(test_invoke([&](auto MK_DEP)RETURNS(s+MK_DEP(s)))(details::make_depend{}));
Compiler explorer demo
EDIT: The variadic operator()
is just to protect against some cases of wrong use of MK_DEP
. I also added a no-argument version for the same reason.