Stack Overflow Asked by cow on December 5, 2021
I am trying to use C++17 if constexpr
for conditional compilation, but it does not behave the way I expect.
For example, with the code below, C++ still compiles the code defined by the macro X2
,
#include <map>
#include <string>
#include <iostream>
#include <type_traits>
#define X1 pp("x")
#define X2 pp("x", "x")
void pp(const std::string str)
{
std::cout << str << std::endl;
}
int main()
{
std::map<std::string, int> map;
if constexpr (std::is_null_pointer_v<decltype(map)>)
X2;
else
X1;
}
and spits out this error messages:
1.c:6:23: error: too many arguments to function ‘void pp(std::__cxx11::string)’
#define X2 pp("x", "x")
^
1.c:18:3: note: in expansion of macro ‘X2’
X2;
^~
How can I skip compilation of X2?
If possible, you can modify the pp
to behave the way I think it should behave:
void pp(auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
}
If that's not possible, then I suggest fixing the X2 to a code that compiles at least.
If changing X2 is not possible, then I suggest shadowing the pp for when calling x2 even though It's the best solution of course:
if constexpr (std::is_null_pointer_v<decltype(map)>) {
auto pp = [] (auto const &...args){
((std::cout << std::forward<decltype(args)>(args)), ...) << std::endl;
};
X2;
} else {
X1;
}
Answered by moisrex on December 5, 2021
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)...);
});
}
Answered by HolyBlackCat on December 5, 2021
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:
constexpr if
mixing.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:
#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
}
Answered by JeJo on December 5, 2021
Outside of a template, even the false branch of an if constexpr
is fully checked. In general, for this, one would need to
#if
pre-processor directive,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.
Answered by cigien on December 5, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP