1

I have a quick & dirty macro which I used to quickly test my program:

#define ASSERT_EQUAL(expected, actualFunc, desc)

expected should be the first argument, and is a literal; actualFunc should be the second argument, and is a function call with parameters, an example call looks like:

ASSERT_EQUAL(true, test1(42), "cache-friendly");

In this macro, I will also timing the second argument(the function call), in order to make sure it works as expected, I need to make sure the parameters are passed in right order, or else it should error out., like below:

ASSERT_EQUAL(test1(42), true, "cache-friendly");

I tried:

static_assert(std::is_fundamental<decltype(expected)::value)

But it doesn't work as it won't error out even if I pass in the function call as first paramter expected, because its return value is a fundamental type.

is there a way to static assert and error out if the order of paramters are not as expected?

FYI - the macro implementation:

static int CaseNum = 0;

#define ASSERT_BASE(expected, actualFunc, desc) \
  std::chrono::steady_clock clock;  \
  auto start = clock.now();  \
        auto actual = actualFunc; \
  auto elapsedTime = clock.now() - start; \
  auto timeInUS = std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime).count(); \
  cout << boolalpha << "CASE " << ++CaseNum << ": " << "EXPECTED=" << expected  << "; ACTUAL=" << actual << "; elapsedTime=" << timeInUS << "us" <<  " --- " <<  desc << endl; \

#define ASSERT_EQUAL(expected, actualFunc, desc) \
{ \
  ASSERT_BASE(expected, actualFunc, desc) \
  assert(expected == actual); \
}

#define ASSERT_NEAR(expected, actualFunc, desc) \
{ \
  ASSERT_BASE(expected, actualFunc, desc) \
  assert(fabs(expected - actual) < 0.0001); \
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
Baiyan Huang
  • 6,463
  • 8
  • 45
  • 72
  • Do you have exact criteria on what should and shouldn't be allowed in each of the arguments? – HolyBlackCat Jan 01 '21 at 10:14
  • Because I want to timing the second parameter, where it actually execute the function, and I want to make my code clean that expected value always comes first when call the macro. @HolyBlackCat – Baiyan Huang Jan 01 '21 at 10:14
  • Yep, just noticed that part of the question. Deleted the original comment. – HolyBlackCat Jan 01 '21 at 10:14
  • For now, it is just simply as: first argument is a literal of fundamental type; second argument is a function call which returns the same fundamental type – Baiyan Huang Jan 01 '21 at 10:16
  • How will those two macro arguments be used? For two expressions `expr1` and `expr2` that doesn't have dependent side-effects (which makes for bad code anyway), comparison for equality is commutative. That is `expr1 == expr2` will yield the same result as `expr2 == expr1`. So are you sure that you really need this assertion? – Some programmer dude Jan 01 '21 at 10:24
  • 2
    *"literal of fundamental type"* Then consider initializing a `constexpr` variable with it. This will allow literals and `constexpr` function calls. – HolyBlackCat Jan 01 '21 at 10:40
  • @Someprogrammerdude I added the macro implementation in the question, the reason is also explained in 2nd comment – Baiyan Huang Jan 01 '21 at 10:46
  • @HolyBlackCat Thanks, this is really brilliant - it shall work good enough for my quick & dirty testing marco! – Baiyan Huang Jan 01 '21 at 10:53

2 Answers2

2

This is not really possible by inspecting the expression within the language, since C++ is continually expanding the horizons of what is allowed in a constant-expression. You could check that expected can be used as a constant expression by e.g. using it to initialize a constinit variable, but this could result in a false negative if the test function is constexpr, which it might well be.

Instead, you could perform textual introspection using the preprocessor "stringification" operator:

static_assert(std::string_view{#expected}.find('(') == std::string_view::npos, "expected must be a literal");

Example.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    Might still have false negative/positive, with some `operator T() const` `ASSERT_EQUAL(MyInt{42}, 42, "!")` or with `const char*`: `ASSERT_EQUAL("(Unknown)", GetName(), "!")`. – Jarod42 Jan 01 '21 at 11:47
  • @Jarod42 very true - although OP said that they expected `expected` to be a fundamental type, i.e. arithmetic or nullptr, which excludes string literals. It's reasonably straightforward to check that a token is an arithmetic literal, at least. – ecatmur Jan 01 '21 at 12:11
  • I was expecting there is a type properities to differentiate a function call, and a literal value, expected something like: `std:is_primary_expression()`, but seems like such thing doesn't exist? – Baiyan Huang Jan 02 '21 at 00:10
  • 1
    @baye yes, it doesn't exist *yet* - the Reflection Study Group (SG7) are working on adding reflection to C++, possibly through a `reflexpr` operator. [This keynote](https://www.youtube.com/watch?v=VMuML6vLSus) is a good introduction. [This answer](https://stackoverflow.com/a/13217106/567292) covers the various progress that has been made toward that goal. – ecatmur Jan 02 '21 at 09:12
2

I might suggest another approach (without MACRO) to enforce that second argument is a function/callable (instead of result of function):

template <typename T, typename F>
void AssertRun(T expected, F f, const std::string& descr)
{
    static int CaseNum = 0;

    std::chrono::steady_clock clock;
    const auto start = clock.now();
    auto actual = f();
    const auto elapsedTime = clock.now() - start;
    const auto timeInUS =
        std::chrono::duration_cast<std::chrono::microseconds>(elapsedTime).count();
    std::cout << boolalpha << "CASE " << ++CaseNum << ": "
              << "EXPECTED=" << expected
              << "; ACTUAL=" << actual
              << "; elapsedTime=" << timeInUS << "us"
              <<  " --- " << desc << std::endl;
    return actual;
}

template <typename T, typename F>
void AssertEqual(T expected, F f, const std::string& descr)
{
    auto actual = AssertRun(expected, f, descr);
    assert(expected == actual);
}

and then

AssertEqual(true, []{ return test1(42); }, "cache-friendly");

AssertEqual(test1(42), []{ return true; }, "cache-friendly"); would still be possible, but seems less probable to confound.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • That is good point, but `F f` needs to be a function call with parameters, that is the key part, a macro would still work if I am passing in function type, rather than a function call – Baiyan Huang Jan 02 '21 at 00:04
  • 1
    Lambda with capture allows to hide the extra parameters. else you can still use variadic template for extra args. but indeed, you have different caller code. – Jarod42 Jan 02 '21 at 00:10