if constexpr Seems to Only Work if Both Cases are Valid
With a constexpr if, the body of the discarded statement must still be syntactically correct, even if it is not compiled. The compiler knows at compile time that
vec.x
is incorrect, so you get an error. If you refactor the code to use a template like
template<typename T>
void foo(T& vec)
{
if constexpr(has_x<T>())
{
cout << vec.x << endl;
}
else
{
cout << size(vec) << endl;
}
}
int main()
{
vector<int> vec;
A my_a{};
std::cout << has_x<decltype(my_a)>() << endl << has_x<decltype(vec)>() << endl;
foo(vec);
}
then
vec.x
is syntactically correct, it doesn't know what vec
is, but it is not malformed code, so it passes. Then, once the template is instantiated, the condition of the if statement is evaluated and the vec.x
is discarded so there is no compiler error.
Typically you only use a constexpr if in template contexts. You don't have to, but if you don't you have to make sure that the body will compile at compile time, even if it would be discarded, just like a plain old if statement.
Even inside templates you still have to be careful. If the body of the constexpr if does not rely on the template parameter, then it will be evaluated before the template is ever instantiated. Using
template <typename T>
void f()
{
if constexpr (std::is_integer_v<T>)
// ...
else
static_assert(false, "T must be an integer type");
}
the code won't compile since static_assert(false, "T must be an integer type")
triggers when the template is parsed. You have to make the condition depend on the template type so it will be evaluated at instantiation time like
template<class T> struct always_false : std::false_type {};
template <typename T>
void f()
{
if constexpr (std::is_integer_v<T>)
// ...
else
static_assert(always_false<T>, "T must be an integer type");
}
Is if constexpr useful outside of templates?
Is it true, that if
expr
is not dependent on a template parameter, then no branches ofif constexpr(expr)
will be discarded? If yes, where does the standard say so? […]
Yes, that is true. You're looking for [stmt.if]/2. Specifically this part:
[…] During the instantiation of an enclosing templated entity, if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated. […]
The best example I could find for a case where you would end up being value-dependent after instantiation is the one given by cppreference.com:
template<class T> void g() {
auto lm = [](auto p) {
if constexpr (sizeof(T) == 1 && sizeof p == 1) {
// this condition remains value-dependent after instantiation of g<T>
}
};
}
Is
if constexpr
useful outside of templates? If yes, can you give some examples to understand its usefulness?
While all branches will be instantiated when the if constexpr
does not appear inside of a template, [basic.def.odr]/10 still applies:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; […]
emphasis mine. That effectively means that an odr-use of an entity in a discarded statement doesn't count. For example:
void blub();
constexpr bool use_blub = false;
void f()
{
if constexpr (use_blub)
{
blub();
}
}
The call to blub()
will not require that your program have a definition of blub()
if the condition is false. Using a normal if
, the program would still be required to provide a definition of blub()
somewhere, even if it is never used. So you could, e.g., use if constexpr
to toggle between calling some library function and calling some fallback implementation depending on whether the library is available (and being linked to). Apart from that, hypothetically, a compiler might not warn about unreachable code if it is unreachable due to an if constexpr
like it potentially would with a normal if
. I couldn't come up with an example of this using any actual compiler, however…
Does if constexpr(something false) ALWAYS omit template instantiation
Does it have the same meaning as in "template instantiation"?
Yes, it does. The specification speaks of "discarded" statements, and has meaning only in the context of template instantiation for some enclosing templated entity.
[stmt.if] (emphasis mine)
2 If the if statement is of the form if constexpr, the value of
the condition shall be a contextually converted constant expression of
type bool; this form is called a constexpr if statement. If the value
of the converted condition is false, the first substatement is a
discarded statement, otherwise the second substatement, if present, is
a discarded statement. During the instantation of an enclosing
templated entity, if the condition is not value-dependent after its
instantiation, the discarded substatement (if any) is not
instantiated.
The important bit is the condition being value-dependent after the parameters are substituted. On one leg, it means that if the condition depends only on the parameters of the immediately enclosing template that is being instantaited, it will not be part of the instantiation of that template.
In your case, it means exactly that if std::is_same_v<void,S>
is true, the body of the "if" won't be part of the instantiated fun<void>
.
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?
One option is to provide a template function for that.
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
andconstexpr 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
}
False-branch of if constexpr not discarded in templated lambda
According to the code linked,
template<bool condition>
void print_fun(foo_bar & obj) {
std::cout << obj.x << std::endl;
if constexpr(condition)
std::cout << obj.y << std::endl;
}
The problem is with if constexpr being used, the statement std::cout << obj.y << std::endl;
is ill-formed for every possible instantiation of the template print_fun
; i.e. no matter what's the value of condition
it's just always ill-formed.
Note: the discarded statement can't be ill-formed for every possible specialization:
The common workaround for such a catch-all statement is a type-dependent expression that is always false:
To fix it you can make the statement to dependent on the template parameter, e.g.
template <bool condition>
using foo_bar = Combined<condition, foo, bar>;
template<bool condition>
void print_fun(foo_bar<condition> & obj) {
std::cout << obj.x << std::endl;
if constexpr(condition)
std::cout << obj.y << std::endl;
}
and use it as
foo_bar<no> obj = {};
print_fun<no>(obj);
Now for obj.y
, obj
is of type foo_bar<condition>
, which depends on the template parameter condition
.
LIVE
Why does this templated struct with C++17 `constexpr if` fail to compile on MSVC?
It's simply a bug for MSVC and GCC.
There is already a bug report for MSVC.
For GCC, it may be related to this bug, and is fixed in GCC trunk.
C++ if constexpr vs template specialization
What do you think which pattern shall I take for my executable to be at minimal size?
In both cases, if you only instantiate createSomething<Type::Something1>
you will get one function definition that is effectively one line of code.
I really do care about size of my executable in the end
Then get rid of the static
. Template functions are implicitly inline, but static
functions will have unique copies for each translation unit.
I know that these two examples are conceptually the same
They are not.
createSomething<void>
is a defined function using Example2, and is undefined using Example 1.
Related Topics
How to Set Timeout for Std::Cin
Struct Initialization of the C/C++ Programming Language
What's the Precedence of Comma Operator Inside Conditional Operator in C++
C++:Creating an Array with a Size Entered by the User
Function Declaration Inside or Outside the Class
How to Get Total CPU Usage in Linux Using C++
Gcc Linker Can't Find Standard Library
How to Validate Input Using Scanf
In C++, Is It Still Bad Practice to Return a Vector from a Function
Super High Performance C/C++ Hash Map (Table, Dictionary)
Why Do We Actually Need Private or Protected Inheritance in C++
C++ Template Copy Constructor on Template Class
What Is/Are the Purpose(S) of Inline
C++ What Is the Purpose of Casting to Void