Sometimes when crafting an interface, we want to ensure that some illegal constructs leads to compilation errors. After all, a good interface is easy to use correctly, and difficult to get wrong, and what can be more difficult to get wrong than something that doesn't compile?
We also know that untested often is buggy, or at least we cannot be sure that it is correct, and tests tests that aren't automated tend to be forgotten. This, of course, means that even if you've carefully crafted an interface where some construction is illegal, and made some manual tests for it, some bug fix later might ruin it and no test will catch that the illegal construct now compiles.
However, namespaces and using directives opens an opportunity. Below is the beginnings of a trap that catches illegal calls to a function named f.
int f(std::string); // function to trap abuse ofWith the aid of the C++11 decltype specifier, we can catch the resulting type of an expression at compile time, in this case getting the return type of a function call, without actually making the call.
struct illegal;
namespace bait {
illegal f(...);
}
using namespace bait;
f(3); // calls bait::f
f(""); // calls ::f
decltype(f(3)) obj1; // illegalNote that bait::f is never implemented. All that is needed is the signature so that the compiler can find match the arguments and get the return type.
decltype(f("")) obj2; // int
With these two, the trap can be triggered at compile time using C++11 static_assert and std::is_same<T,U>
#include <type_traits>The above compiles, since f(3) matches bait::f, and no code is generated. Changing to a match of ::f, however
static_assert(std::is_same<decltype(f(3)),illegal>::value,
"compiles when it shouldn't");
static_assert(std::is_same<decltype(f("")),illegal>::value,gives a compilation error. On clang++ 3.4.2 the message is:
"compiles when it shouldn't");
fc.cpp:18:1: error: static_assert failed "compiles when it shouldn't"and g++ 4.8.2 gives the message:
static_assert(std::is_same<decltype(f("")), illegal>::value,
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
fc.cpp:18:1: error: static assertion failed: compiles when it shouldn'tThe results seem reversed. A call that would cause compilation error compiles, and a call that would compile gives a compilation error.
static_assert(std::is_same<decltype(f("")), illegal>::value,
^
Now what if f is in a namespace? Things becomes marginally more cluttered, but the technique remains the same. Putting the bait in an inline namespace solves the problem.
namespace ns {An inline namespace is a namespace, but a everything declared in it is visible in its surrounding namespace, similar to the using namespace directive used earlier.
int f(std::string);
}
struct illegal;
namespace ns {
inline namespace bait {
illegal f(...);
}
}
ns::f(3); // calls ns::bait::fThe test thus becomes:
ns::f(""); // calls ns::f
static_assert(std::is_same<decltype(ns::f(3)),illegal>::value,A macro can help with code readability:
"compiles when it shouldn't");
#define ASSERT_COMPILATION_ERROR(...) \Writing the test code as:
static_assert(std::is_same<decltype(__VA_ARGS__), \
illegal::type>::value, \
#__VA_ARGS__ " compiles when it shouldn't")
ASSERT_COMPILATION_ERROR(ns::f(""));gives the (g++ 4.8.2) compilation error:
fc.cpp:20:3: error: static assertion failed: ns::f("")compiles when it shouldn'tI think this is pretty neat. It is now simple to test that illegal calls actually don't compile. You can add these tests to the unit test program that asserts the intended functionality.
static_assert(std::is_same<decltype(__VA_ARGS__), illegal>::value, \
^
fc.cpp:23:1: note: in expansion of macro ‘ASSERT_COMPILATION_ERROR’
ASSERT_COMPILATION_ERROR(ns::f(""));
^
No comments:
Post a Comment