C++11 challenge: ASSERT_INVALID_EXPR

I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above. Any takers? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Sun, 4 Dec 2011, Dave Abrahams wrote:
I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above.
Any takers?
Here's the best I've been able to come up with. It works on GCC 4.6 (I tested with a prerelease, so the bug I mention might have been fixed). It only works within classes, and you need to use a separate template parameter in the tested expression (it would be nice if you could shadow template parameters in a nested class, BTW). -- Jeremiah Willcock #include <utility> #include <boost/preprocessor/cat.hpp> #include <boost/mpl/bool.hpp> template <typename T> struct identity {typedef T type;}; template <typename U> struct test_template { #define BOOST_ASSERT_INVALID_EXPR(tparam, e, msg) \ struct BOOST_PP_CAT(invalid_expr_test, __LINE__) { \ template <typename BOOST_PP_CAT(tparam, _), typename = decltype((e))> struct expr_type {typedef void type;}; \ static boost::mpl::bool_<false> is_ok(bool); \ template <typename T> static boost::mpl::bool_<true> is_ok(T*, const typename expr_type<T>::type* = 0); \ }; \ /* identity here works around parsing bug in GCC 4.6 (decltype(foo)::value doesn't work) */ \ static_assert(identity<decltype(BOOST_PP_CAT(invalid_expr_test, __LINE__)::is_ok((tparam*)0))>::type::value, msg); BOOST_ASSERT_INVALID_EXPR(U, std::declval<U_>() + "bar", "valid"); }; #if 0 // This requires the ability to put member templates in local classes template <typename T> void test2(T*) { BOOST_ASSERT_INVALID_EXPR(T, "foo" + std::declval<T_>(), "valid2"); } #endif int main(int, char**) { test_template<int> a; test_template<char*> b; #if 0 test2((int*)0); test2((char**)0); #endif return 0; }

On 04/12/11 17:20, Jeremiah Willcock wrote:
On Sun, 4 Dec 2011, Dave Abrahams wrote:
I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above.
Any takers?
Here's the best I've been able to come up with. It works on GCC 4.6 (I tested with a prerelease, so the bug I mention might have been fixed). It only works within classes, and you need to use a separate template parameter in the tested expression (it would be nice if you could shadow template parameters in a nested class, BTW).
Both responses to this request so far have given an example where the expression uses types, rather than values. I think we can do better if we use values, precisely because we can shadow value names, where we can't shadow template parameters. Again, I can only make it work in a class context. For example, the following code can be made to do the right thing: template<typename T, typename U> struct A { T x; U y; ASSERT_INVALID_EXPR((x), x+y, "Can add x and y; bad"); }; int main() { A<char*, char*>(); A<int, int>(); // generates assertion failure } Here the second argument to ASSERT_INVALID_EXPR is the expression, and the first is a PP_SEQ of some values used in it (enough such that every sub-expression which might be invalid uses one of the values). So, instead of "(x)", we could have passed "(x)(y)" here and it would have worked just as well. The macro in this case expands to something which is essentially: struct invalid_expr_test { template<typename T0> static boost::mpl::false_ is_ok(int*, T0&& x, decltype(x+y)* = 0); template<typename T0> static boost::mpl::true_ is_ok(bool, T0&&); typedef decltype(is_ok((int*)0, x)) test_type; }; static_assert(invalid_expr_test::test_type::value, "Can add x and y; shouldn't be able to");; By shadowing the specified variable names (in this case x) we make the expression a type-dependent expression, and avoid the hard error. I attach the implementation with all the gory preprocessor metaprogramming details. Tested with g++ 4.5.2, g++ 4.6.2, and clang++ trunk 143505. John Bytheway

Hi guys, Just wanted to say sorry for the radio silence and thanks for making the effort. These are great solutions but I guess the whole idea is less interesting than I thought it was when I proposed it. SFINAE is still pretty limited in what it can detect without causing an error. on Sun Dec 04 2011, Jeremiah Willcock <jewillco-AT-osl.iu.edu> wrote:
On Sun, 4 Dec 2011, Dave Abrahams wrote:
I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above.
Any takers?
Here's the best I've been able to come up with. It works on GCC 4.6 (I tested with a prerelease, so the bug I mention might have been fixed). It only works within classes, and you need to use a separate template parameter in the tested expression (it would be nice if you could shadow template parameters in a nested class, BTW).
-- Jeremiah Willcock
#include <utility> #include <boost/preprocessor/cat.hpp> #include <boost/mpl/bool.hpp>
template <typename T> struct identity {typedef T type;}; template <typename U> struct test_template { #define BOOST_ASSERT_INVALID_EXPR(tparam, e, msg) \ struct BOOST_PP_CAT(invalid_expr_test, __LINE__) { \ template <typename BOOST_PP_CAT(tparam, _), typename = decltype((e))> struct expr_type {typedef void type;}; \ static boost::mpl::bool_<false> is_ok(bool); \ template <typename T> static boost::mpl::bool_<true> is_ok(T*, const typename expr_type<T>::type* = 0); \ }; \ /* identity here works around parsing bug in GCC 4.6 (decltype(foo)::value doesn't work) */ \ static_assert(identity<decltype(BOOST_PP_CAT(invalid_expr_test, __LINE__)::is_ok((tparam*)0))>::type::value, msg); BOOST_ASSERT_INVALID_EXPR(U, std::declval<U_>() + "bar", "valid"); };
#if 0 // This requires the ability to put member templates in local classes
template <typename T> void test2(T*) { BOOST_ASSERT_INVALID_EXPR(T, "foo" + std::declval<T_>(), "valid2"); } #endif
int main(int, char**) { test_template<int> a; test_template<char*> b; #if 0 test2((int*)0); test2((char**)0); #endif return 0; }
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
on Mon Dec 05 2011, John Bytheway <jbytheway+boost-AT-gmail.com> wrote:
On 04/12/11 17:20, Jeremiah Willcock wrote:
On Sun, 4 Dec 2011, Dave Abrahams wrote:
I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above.
Any takers?
Here's the best I've been able to come up with. It works on GCC 4.6 (I tested with a prerelease, so the bug I mention might have been fixed). It only works within classes, and you need to use a separate template parameter in the tested expression (it would be nice if you could shadow template parameters in a nested class, BTW).
Both responses to this request so far have given an example where the expression uses types, rather than values. I think we can do better if we use values, precisely because we can shadow value names, where we can't shadow template parameters. Again, I can only make it work in a class context. For example, the following code can be made to do the right thing:
template<typename T, typename U> struct A { T x; U y; ASSERT_INVALID_EXPR((x), x+y, "Can add x and y; bad"); };
int main() { A<char*, char*>(); A<int, int>(); // generates assertion failure }
Here the second argument to ASSERT_INVALID_EXPR is the expression, and the first is a PP_SEQ of some values used in it (enough such that every sub-expression which might be invalid uses one of the values). So, instead of "(x)", we could have passed "(x)(y)" here and it would have worked just as well. The macro in this case expands to something which is essentially:
struct invalid_expr_test { template<typename T0> static boost::mpl::false_ is_ok(int*, T0&& x, decltype(x+y)* = 0);
template<typename T0> static boost::mpl::true_ is_ok(bool, T0&&);
typedef decltype(is_ok((int*)0, x)) test_type; }; static_assert(invalid_expr_test::test_type::value, "Can add x and y; shouldn't be able to");;
By shadowing the specified variable names (in this case x) we make the expression a type-dependent expression, and avoid the hard error. I attach the implementation with all the gory preprocessor metaprogramming details.
Tested with g++ 4.5.2, g++ 4.6.2, and clang++ trunk 143505.
John Bytheway
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 12/04/2011 05:15 PM, Dave Abrahams wrote:
I don't know whether this is actually possible, but I was thinking today that there might be a way to use the new SFINAE to detect errors in a large class of invalid expressions without actually causing a hard error, and thereby build an assertion that fails compilation when passed a valid expression and passes for any invalid expression in the large class mentioned above.
Any takers?
Why not just something like template<class T> decltype(my_expr_that_depends_on_T, char) foo(int); template<class T> int foo(...); static const bool valid = sizeof(foo<T>(0)) == sizeof(char); BOOST_MPL_ASSERT(!valid, ...) I suppose the problem with the macro will be to be able to find a nice syntax to specify the template arguments the expression must depend on.
participants (4)
-
Dave Abrahams
-
Jeremiah Willcock
-
John Bytheway
-
Mathias Gaunard