2

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
Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265

1 Answers1

6

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

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265