Why does this usage of C++17 if constexpr fail?
This is not possible outside the template!
From cppreference.com
Outside a template, a discarded statement is fully checked.
if constexpr
is not a substitute for the#if
preprocessing directive:void f() { if constexpr(false) { int i = 0; int *p = i; // Error even though in discarded statement } }
Any idea how to skip compilation of X2?
template<typename T>
void test()
{
if constexpr (std::is_null_pointer_v<T>)
X2;
else
X1;
}
int main()
{
std::map<std::string, int> map;
test<decltype(map)>(); // now chooses the X1
}
Thanks to @HolyBlackCat and @MSalters. As they pointed out, the above solution is ill-formed NDR (therefore, compiling with MSVC compiler does not make any sense and on the other hand the GCC and clang at least catch this by providing some compiler errors ) which has been detailed in the @HolyBlackCat's, answer!
Therefore can we skip the compilation of X2?
Unfortunately, NO as per your code!!
The preprocessor will be executed before the compilation of the translation unit.
Therefore one can not provide the type information (i.e. decltype(map)
) to #if
directives.
Hence for your case, there is no other way.
Good lessons from this post:
- Your program is, however, is a good example to avoid such kind macro
and
constexpr if
mixing. - Secondly, check the correctness of the code by different compilers if possible!
I would suggest having a function overload for PP
(and of course there are many other ways) to your case, by which you could get a well-formed code:
See a demo.
#include <string>
#include <iostream>
#include <type_traits>
#include <map>
void pp(const std::string& str)
{
std::cout << str << std::endl;
}
template<typename... T> void pp(const T&... args)
{
// do something with args!
}
template<typename T>
constexpr void test()
{
if constexpr (std::is_null_pointer_v<T>)
pp("x", "x"); // call with args
else
pp("x"); // call with string
}
if constexpr
is not really a "conditional compilation".
Outside of a template, it works just like the regular if
(except it wants the condition to be constexpr
).
The other answers suggest putting it inside of a template (and making the condition depend on the template parameter), but that's not enough. (It seems to work in MSVC, but not in GCC & Clang.) That's because:
[temp.res]/8.1
(emphasis mine)The validity of a template may be checked prior to any instantiation. ... The program is ill-formed, no diagnostic required, if:
— no valid specialization can be generated for a template or a substatement of a constexpr if statement within a template and the template is not instantiated, ...
So if you can't make a valid instantiation for an if constexpr
branch (that is, if for all possible template arguments the branch is invalid), then the program is ill-formed NDR (which effectively means "invalid, but the compiler might not be smart enough to give you an error").
(As noted by @MSalters, the standard says "and the template is not instantiated", rather than "and the template or the substatement of the constexpr if are not instantiated". I argue that it's a defective wording, because it makes no sense otherwise: there doesn't seem to be any other wording to check validity of discarded branches, so it would make the code well-formed only when the enclosing template is instantiated, and ill-formed NDR otherwise. See discussion in the comments.)
There seem to be no workarounds for that, and no good solutions for your problem.
You could make the function call itself depend on the template parameter, but it's probably cheating, as it requires shadowing pp
(or doing #define pp …
).
template <typename F>
void test(F pp) // Note parameter shadowing the global `pp` for the macros.
{
std::map<std::string, int> map;
if constexpr (std::is_null_pointer_v<decltype(map)>)
X2;
else
X1;
}
int main()
{
test([](auto &&... params)
{
pp(decltype(params)(params)...);
});
}
Outside of a template, even the false branch of an if constexpr
is fully checked. In general, for this, one would need to
- either use a
#if
pre-processor directive, - or put the
if constexpr
code into a template.
In your case, you can not use #if
because your condition depends on type information that is not available to the pre-processor.
Also, you can not use constexpr if
because the expansion of the macro X2
is always ill-formed, for any possible template parameter.
You probably need to rethink why you want to have a macro whose expansion is never valid.