4

With Boost Test earlier than version 1.64, you cannot do this:

SomeType* a_pointer = getPointer();
BOOST_CHECK_EQUAL(a_pointer, nullptr);

This is because nullptr_t has ambiguous overloads: Boost issue #12778 and a related SO question. As in the answers to the question, this can be resolved easily:

BOOST_CHECK(!a_pointer); // rely on boolean casting, or...
// cast nullptr away from nullptr_t
BOOST_CHECK_EQUAL(a_pointer, static_cast<SomeType*>(nullptr));

However, if you support multiple boost versions, it's easy for a BOOST_CHECK_EQUAL(a_pointer, nullptr) to slip by on a newer platform, and break older ones.

One solution here is to enforce a CI platform with older Boost versions (which is useful for other reasons too, especially when the supported Boost versions extended before 1.59, when Boost Test 3 changed a lot!).

However, relying only on CI to catch this is a big delay in the OODA loop (compared to a local compiler failure) and requires network access and an simple-but-annoying VCS dance to patch the trivial changes in and resubmit the job.

Is there a way to cause it to fail to compile, even when the Boost version would otherwise support it?

Inductiveload
  • 6,094
  • 4
  • 29
  • 55
  • Well, if you want to support multiple boost versions you will need to run tests on each of the supported version anyway. And if primary concern here is the delay caused by resubmitting this to CI system then how about compiling these tests locally or developing using oldest supported boost version and checking for forward portability instead? – user7860670 Feb 06 '19 at 10:42
  • @VTT it's more a curiosity thing than a critical thing. It's just annoying to have to wait for an hour-plus CI job (or maintain "n" local builds) if there is something I can throw in the existing Boost version polyfill/patches/compatibility header. The real answer is, of course, full CI coverage, but it's a bit wasteful if you can avoid failed jobs in the first place (and if the compiler can detect it, you get an IDE marker too!) – Inductiveload Feb 06 '19 at 10:49

1 Answers1

3

In Boost Test, this is implemented in commit 229e71199 for v1.64, using the print_log_value customization point:

template<>
struct BOOST_TEST_DECL print_log_value<std::nullptr_t> {
    void operator()( std::ostream& ostr, std::nullptr_t t ) {
        ostr << "nullptr";
    }
};

It is not possible to "undef" a function defined in a different translation unit (barring some pretty nasty preprocessor hacks). So it's not really possible to "damage" the function and cause a compilation failure if you try to use the function.

However we have two better choices: use the Boost test method to avoid printing this type, or do it ourselves.


Avoid printing nullptr_t

You use the existing Boost customization point for preventing logging a type: BOOST_TEST_DONT_PRINT_LOG_VALUE:

// in your common test header
BOOST_TEST_DONT_PRINT_LOG_VALUE( std::nullptr_t )

What this does, since in Boost 1.59 is define a print_log_value function that does nothing:

#define BOOST_TEST_DONT_PRINT_LOG_VALUE( the_type )         \
namespace boost{ namespace test_tools{ namespace tt_detail{ \
template<>                                                  \
struct print_log_value<the_type > {                         \
    void    operator()( std::ostream&, the_type const& ) {} \
};                                                          \
}}}                                                         \                                                     

Before 1.59 (commit bae8de14b), it's defined differently (not in tt_detail for a start), but the idea is the same. This means it will work back to at least 1.58 and earlier using this macro.

However, because in 1.64, the print_log_value function was defined, if you just add the above macro, you will end up with redefinition errors from 1.64 onwards: one that does nothing from the DONT_PRINT macro, and the one that prints "nullptr". So you can guard it with the relevant Boost version:

#if BOOST_VERSION < 106400
    BOOST_TEST_DONT_PRINT_LOG_VALUE( std::nullptr_t )
#endif

Now it will avoid printing nullptr on Boost < 1.64, and it will print on 1.64+:

[ != 0xdeadbeef]        // < 1.64, using BOOST_TEST_DONT_PRINT_LOG_VALUE
[nullptr != 0xdeadbeef] // 1.64 +, using built-in implementation

This might be good enough if you don't really care about the beauty of the logging on older Boost versions.


Do it yourself

You can also implement you own print_log_value customisation point. However, note that the namespace is different prior to 1.59, and we should only do it for <1.64, as, again, we'd be redefining the function:

// You don't need this bit if you don't support Boost Test <1.59.
#if BOOST_VERSION >= 105900
#    define BOOST_TEST_PRINT_NAMESPACE_OPEN namespace boost { namespace test_tools { namespace tt_details {
#    define BOOST_TEST_PRINT_NAMESPACE_CLOSE }}}
#else
#    define BOOST_TEST_PRINT_NAMESPACE_OPEN namespace boost { namespace test_tools {
#    define BOOST_TEST_PRINT_NAMESPACE_CLOSE }}
#endif

#if BOOST_VERSION < 106400

BOOST_TEST_PRINT_NAMESPACE_OPEN

template<>
struct print_log_value<nullptr_t> {
    inline void operator()(std::ostream& os, nullptr_t const& p) {
        os << "nullptr";
    }
};

BOOST_TEST_PRINT_NAMESPACE_CLOSE

#endif // End <1.64 condition

Now it will print the same thing:

[nullptr != 0xdeadbeef] // < 1.64, using DIY implementation
[nullptr != 0xdeadbeef] // 1.64 +, using built-in implementation
Inductiveload
  • 6,094
  • 4
  • 29
  • 55