"If Constexpr" in C++17 Does Not Work in a Non-Templated Function

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 of if 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
    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
}

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



Leave a reply



Submit