c++templatesmacrosgoogletestis-same

Google test (gtest) `EXPECT_TRUE()` macro won't compile with `std::is_same<>` template as input


In C++17 with clang compiler, I get the same build errors whether I do this:

EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);

or this:

EXPECT_TRUE(typename std::is_same_v<decltype(var1), decltype(var2)>);,

or this:

EXPECT_TRUE(typename std::is_same_v<typename decltype(var1), typename decltype(var2)>);

Build command:

bazel test //my_target_dir:my_target

Build error:

error: too many arguments provided to function-like macro invocation
                decltype(var2)>);
                ^
gtest/gtest.h:1980:9: note: macro 'EXPECT_TRUE' defined here
#define EXPECT_TRUE(condition) \
        ^
myfile.cpp:125:5: error: use of undeclared identifier 'EXPECT_TRUE'
    EXPECT_TRUE(std::is_same_v<
    ^

Note that the Googletest definition for EXPECT_TRUE() is here: https://github.com/google/googletest/blob/master/googletest/include/gtest/gtest.h#L1980.

What is wrong with what I'm doing, and how can I get this to compile?

References:

  1. std::is_same<T, U>::value and std::is_same_v<T, U>
  2. GoogleTest (gtest) documentation

Solution

  • Summary

    This does NOT work, because the C++ preprocessor which processes macros was written before templates existed, and sees the comma as separating two separate arguments to the macro. It thinks I have called the EXPECT_TRUE() macro with anything<foo as the 1st argument, and bar> as the 2nd argument:

    // This does NOT work, because the preprocessor sees this 1 template
    // argument to the macro as two separate arguments separated by the
    // comma
    EXPECT_TRUE(anything<foo, bar>);
    

    These options DO work:

    // Option 1: move the template outside of the macro call
    bool isSameType = std::is_same_v<decltype(var1), decltype(var2)>;
    EXPECT_TRUE(isSameType);
    
    // Option 2: for this particular case I can instead use the 
    // `static_assert()` function in place of the `EXPECT_TRUE()` macro
    static_assert(std::is_same_v<decltype(var1), decltype(var2)>);
    
    // Option 3: use double parenthesis to force the macro to treat
    // the parameter containing comma-separated template parameters
    // as a **single argument** to the macro:
    EXPECT_TRUE((std::is_same_v<decltype(var1), decltype(var2)>));
    

    Details

    After spending some time chatting with some friends, one of them, Drew Gross, explained the following:

    Due to the C++ preprocessor model, commas within a template instantiation are interpreted as separating arguments of the macro, not arguments of the template. This is one of the many reasons that using macros is heavily discouraged in modern C++. So when you write:

    SOME_MACRO(some_template<a, b>());
    

    it's interpreted as passing 2 arguments to SOME_MACRO, the first being some_template<a, and the second being b>(). Since EXPECT_TRUE only accepts a single argument, this fails to compile.

    In this particular case I'd recommend using static_assert instead of EXPECT_TRUE. If the thing you were testing wasn't a compile time constant, you would have to assign to a local variable first, e.g.

    bool localVar = some_template<a, b>();
    EXPECT_TRUE(localVar);
    

    He's spot-on correct. Since the C and C++ macro preprocessor was written PRIOR to C++ existing, it does not recognize the C++ < and > template scoping symbols (it thinks they are just "less than" and "greater than" symbols, respectively), so in this statement (EXPECT_TRUE(std::is_same_v<decltype(var1), decltype(var2)>);), it sees the comma and parses std::is_same_v<decltype(var1) as the 1st argument to the gtest EXPECT_TRUE() macro, and decltype(var2)> as a 2nd argument to the macro.

    Therefore, here's the solution:

    bool isSameType = std::is_same_v<decltype(var1), decltype(var2)>;
    EXPECT_TRUE(isSameType);
    

    As Drew states, however, a better solution is to just use static_assert() in this case instead of gtest's EXPECT_TRUE(), since this test can be completed at compile-time rather than run-time:

    (better solution):

    static_assert(std::is_same_v<decltype(var1), decltype(var2)>);
    

    Note: message not required for static_assert() in C++17. See here.

    I did some additional research and experimenting, and also discovered that extra parenthesis solve it too. Using extra parenthesis forces the preprocessor to recognize the whole input argument as 1 argument to the macro, since the preprocessor respects parenthesis but doesn't respect template <> symbols at all since it isn't template-aware.

    Therefore, this works too:

    EXPECT_TRUE((std::is_same_v<decltype(var1), decltype(var2)>));
    

    If in doubt, parenthesize it out. If in need, parenthesis, indeed. :)

    So, now we have 3 viable solutions to this problem. I'd probably go with the static_assert() option as my primary solution, and the extra parenthesis option just above as my solution if I needed some template input to be tested during run-time.

    Additional References:

    Once I knew what the nature of the problem was (the macro preprocessor seeing the comma and not recognizing C++ template < and > scoping operators), I was able to do some Googling and find the following answers to look at too:

    1. Too many arguments provided to function-like macro invocation
    2. Getting too many arguments provided to function-like macro invocation compile error while defining lambda inside assert (assert.h) in Xcode [c++]

    Keywords: macro watch out for template parameter inputs; comma argument delimiter to C/C++ macro preprocessor, c++ extra parenthesis required in macros around macro parameters