From the description on cppreference.com, I was under the impression that std::disjunction is designed to give me short-circuiting at compile-time, so that I can use it like this:
#include <type_traits>
#include <iostream>
template<nullptr_t null = nullptr>
constexpr bool does_not_compile() {
static_assert(null != nullptr);
return false;
}
void hello_world () {
if constexpr (std::disjunction_v<std::true_type, std::bool_constant<does_not_compile()>>) {
std::cout << "Hello World!" << std::endl;
}
}
However, this does not compile, std::disjunction does not short-circuit in the sense that the above static_assert doesn't trigger (live example).
But then it what sense is it short-circuiting? It can't be the usual behavior of || at run time, because the type of the std::disjunction has to be known at compile time, and it depends on its value.
You find an explanation right on the page you linked to:
Disjunction is short-circuiting: if there is a template type argument Bi with bool(Bi::value) != false, then instantiating disjunction<B1, ..., BN>::value does not require the instantiation of Bj::value for j > i
The short-circuiting behavior concerns the value member of each parameter type, not the parameter type itself. You cannot instantiate a template without knowing it's paramters. And using std::disjunction<…> will generally require an instantiation. In your example
std::disjunction_v<std::true_type, std::bool_constant<does_not_compile()>>
the compiler still has to instantiate std::bool_constant<does_not_compile()> so that it knows what the whole std::disjunction<…> comes out to be (as you have noted yourself). What is guaranteed is that it won't instantiate std::bool_constant<does_not_compile()>::value…
Related
I am trying to use SFINAE to disable certain functions of a class based on some non-templated enum arguments.
The following code does NOT compile with gcc, but appears to compile and work as expected when using the msvc compiler.
#include <iostream>
#include <type_traits>
enum class B { VARIANT1, VARIANT2 };
template<B B_VAL>
struct A {
template<class = std::enable_if_t<B_VAL == B::VARIANT1>>
void func1() {
std::cout<<"VARIANT1"<<std::endl;
}
template<class = std::enable_if_t<B_VAL == B::VARIANT2>>
void func2() {
std::cout<<"VARIANT2"<<std::endl;
}
};
int main()
{
A<B::VARIANT1> a;
a.func1();
}
The expected (and msvcs) behavior is that calling a function whose enable_if_t condition equates to false results in a compile time error, or the removal of the function candidate for overload resolution if an overloaded function was present in the example. In all other cases, the code should compile normally.
gcc on the other hand tells me that it can't find a type named "type" in "struct std::enable_if<false, void>" for the enable_if_t in the template of func2, which makes perfect sense as the member named "type" is only present in enable_if if the condition equates to true. But shouldn't this be the desired behavior for the SFINAE functionality and shouldn't the compiler ignore func2, as it is never called?
I now have three questions:
As the two compilers produce different behavior, is it undefined and if yes, which parts/statements?
Is SFINAE suited to achieve my goal, or have I misunderstood its use case?
Would I be better off by using static asserts as alternative?
I am sorry if this question is a duplicate of this one, but I don't think that the answers there provided much help with my problem.
GCC is right. And it's because you aren't using SFINAE for your function. It may appear that you do because you employ utilities for SFINAE from the standard library, but there is a crucial ingredient missing here.
The 'S' in "SFINAE" stands for substitution. The substitution of template arguments into the parameters of a template we are trying to instantiate. Now, the template in question is func2. And for SFINAE to work, it is func2's argument that must fail to be substituted for its parameters. But here
std::enable_if_t<B_VAL == B::VARIANT2>
There is no parameter of func2 in use. It doesn't depend on anything that happens during substitution into func2. It's just an invalid type, completely independent of an attempt to actually instantiate func2.
It's not hard to fix though
template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT1>>
void func1() {
std::cout<<"VARIANT1"<<std::endl;
}
template<B B_VAL_ = B_VAL, class = std::enable_if_t<B_VAL_ == B::VARIANT2>>
void func2() {
std::cout<<"VARIANT2"<<std::endl;
}
Now, the check is against the substitution into the correct template.
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
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 (8.6); 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 instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr
The following code does everything I need from it in every C++11 and later compiler I have tried. So, in practice, for my purposes, it works (and is expected to work, at least on Linux, in the foreseeable future, due to historical reasons). However, from a language lawyer perspective, this code is invalid, as it contains a construct (dereference of a pointer to a nonexistent object) that is formally an UD, even though this dereference is actually never executed.
#include <type_traits>
#include <iostream>
namespace n1 {
struct int_based { int base = 0; };
inline int get_base(int_based ib) { return ib.base; }
}
namespace n2 {
struct double_based;
double get_base(const double_based&);
}
template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));
int main() {
auto isInt = std::is_same<base_type<n1::int_based>, int>::value;
auto isDouble = std::is_same<base_type<n2::double_based>, double>::value;
auto unlike = std::is_same<base_type<n1::int_based>, double>::value;
std::cout << isInt << isDouble << unlike << std::endl;
return 0;
}
The code does Koenig lookup for type mapping and infers the mapped type using function signatures that I would not want to change. In this example, the double_based type is incomplete; in my real use cases, the types are expected to be complete but are not guaranteed to be DefaultConstructible. The actual code is a part of a type-safe serialization logic.
The question is: is there a standard-compliant way of "materializing" an object of the template parameter type T for use in decltype in this code, or is it impossible to have such standard-complying type mapping without having a preconstructed object of the source type?
Replacing function parameters with pointers to the objects is ugly and doesn't really solve the problem, as it is unclear what these functions need to do with the nullptr argument in the general case without introducing yet another UB.
What you are looking for is std::declval. It is a function that returns the type you give it so you can work with an object of that type in a unevaluated context. That turns
template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));
into
template <typename T>
using base_type = decltype((get_base(std::declval<T>())));
Do note that there is no requirement of having a definition for std::declval. If you try to use
T foo = std::declval<T>();
then your program is ill-formed.
Actually, this code is fine. (It is true that it is UB to dereference a null pointer and bind the result to a reference, or to dereference a null pointer and access the resulting lvalue. However, when the execution of the program does not actually evaluate these constructs, there is no UB.)
But it is true that std::declval<T>() is the preferred idiom. Unlike the null pointer trick, std::declval<T>() is "safe": if you accidentally use it in a potentially evaluated context, there will be a compile-time error. It is also much less ugly.
You might use std::declval:
template <typename T>
using base_type = decltype((get_base(std::declval<T>())));
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
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 (8.6); 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 instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr
I tried to play with the C++17 standard. I tried to use one of the features of C++17 if constexpr. And I had a problem... Please take a look at the following code. This compiles without errors. In the following code, I tried to use if constexpr to check if it is a pointer.
#include <iostream>
#include <type_traits>
template <typename T>
void print(T value)
{
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Ok
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
auto n = 1000;
print(n);
print(&n);
}
But when I rewrite the above code, as shown below, where if constexpr is in the main function:
#include <iostream>
#include <type_traits>
int main()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
I get a compilation error:
main.cpp:8:32: error: invalid type argument of unary ‘*’ (have ‘int’)
std::cout << "Ptr to " << *value << std::endl;
Problem is not in the main function. This can be any function similar to the following.
void print()
{
auto value = 100;
if constexpr (std::is_pointer_v<decltype(value)>)
std::cout << "Ptr to " << *value << std::endl; // Error
else
std::cout << "Ref to " << value << std::endl;
}
int main()
{
print();
}
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
I would like to know why "if constexpr" works only in template functions, even if the type is deduced by the decltype from the input parameter.
This is by design.
if constexpr will not instantiate the branch not taken if it's within a template. It won't just treat the branch not taken as token soup and avoid parsing it or performing semantic analysis entirely. Both sides are still going to be analyzed, and since *value is ill-formed for ints, that's an error.
You simply can't use if constexpr to avoid compiling non-template code. It's only to avoid instantiating template code that's potentially invalid-for-the-particular-specialization.
C++ standard, clause 9.4.1:
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 (8.6); 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 instantiation of an enclosing templated entity (Clause 17), if the condition is not value-dependent after its instantiation, the discarded substatement (if any) is not instantiated.
(emphasis mine)
So, a substatement of a constexpr if still gets instantiated if it is not inside a template, thus it must at least compile.
I would like to know why if constexpr works only in template functions, even if the type is deduced by the decltype from the input parameter.
The thing is, it also work in non template, just not in the way you would expect.
For if constexpr to work like you stated, you not only need a template, but you need the contained expressions to be dependent on the template parameters.
Let's go step by step why this is made that way in C++, and what are the implications.
Let's start simple. Does the following code compile?
void func_a() {
nonexistant();
}
I think we will all agree that it won't compile, we are trying to use a function that hasn't been declared.
Let's add one layer.
Does the following code compile?
template<typename T>
void func_b_1() {
nonexistant();
}
With a correct compiler, this will not compile.
But why is that? You could argue that this code is never actually compiled, since the template is never instantiated.
The standard define something they call two phase name lookup. This is that even if the template is not instantiated, the compiler must perform name lookup an anything that don't depend on the template parameter.
And that make sense. If the expression nonexistant() don't depend on T, why would its meaning change with T? Hence, this expression is the same as in func_a in the eye of the compiler.
So how about dependent names?
template<typename T>
void func_b_2() {
T::nonexistant();
}
This will compile! Why is that? Nowhere in this code there's a function called nonexistant. Yet, you feed that into a compiler as the whole codebase and it will gladly accept it.
And the standard even says that it has to accept it. This is since there could be a T containing nonexistant somewhere. So if you instantiate the template with a type that has the static member function nonexistant it will compile and call the function. If you instantiate the template with a type that don't have the function, it won't compile.
As you can see, the name lookup is done during instantiation. This is called second phase name lookup. The second phase name lookup is done only during instantiation.
Now, enter if constexpr.
To make such construct working well with the rest of the language properly, it has been decided that if constexpr is defined as a branch for instantiation. As such, we can make some code non-instantiated, even in non templates!
extern int a;
void helper_1(int*);
void func_c() {
if constexpr (false) {
helper_1(&a);
}
}
The answer is that helper_1 and a are not ODR used. We could leave helper_1 and a not defined and there would not be linker errors.
Even better, the compiler won't instantiate templates that are in a discarded branch of a if constexpr:
template<typename T>
void helper_2() {
T::nonexistant();
}
void func_d() {
if constexpr (false) {
helper_2<int>();
}
}
This code won't compile with a normal if.
As you can see, the discarded branch of a if constexpr work just like a template that hasn't been instantiated, even in non template code.
Now let's mix it up:
template<typename T>
void func_b_3() {
if constexpr (false) {
nonexistant();
}
}
This is just like our template function in the beginning. We said that even if the template was not instantiated, the code was invalid, since the invalid expression don't depend on T. We also said that if constexpr is a branch in the instantiation process. The error happen before instantiation. This code won't compile either.
So finally, this code won't compile either:
void func_e() {
if constexpr (false) {
nonexistant();
}
}
Even though the content of the if constexpr is not instantiated, the error happen because the fist name lookup step is done, and the error happen before the instantiation process. It is just that in this case, there is no instantiation, but it doesn't matter at this point.
So what are the uses of if constexpr? Why does it seem to work only in templates?
The thing is, it doesn't work differently in templates. Just as we saw with func_b_3, the error still happen.
But, this case will work:
template<typename T>
void helper_3() {
if constexpr (false) {
T::nonexistant();
}
}
void func_f() {
helper_3<int>();
}
The expression int::nonexistant() is invalid, but the code compile. This is because since T::nonexistant() is an expression that depends on T, name lookup is done in the second phase. The second phase of name lookup is done during template instantiation. The if constexpr branch that contain T::nonexistant() is always the discarded part so the second phase of name lookup is never done.
There you go. if constexpr is not about not compiling a portion of code. Just like template, they are compiled and any expression that name lookup can be done is done. if constexpr is about controlling instantiation, even in non template function. All rules that applies to templates also applies to all branch of the if constexpr. Two phase name lookup still applies and allow programmers to not instantiate some part of the code that would otherwise not compile if instantiated.
So if a code cannot compiled in a template that is not instantiated, it won't compile in the branch of the if constexpr that is not instantiated.
Outside a template, a discarded statement is fully checked. if constexpr is not a substitute for the #if preprocessing directive.
here
I am a little late to the party but a nice trick I use when I need if constexpr in a non template function is to wrap my code in a lambda.
struct Empty
{
};
void foo()
{
/* foo is not template, will not compile
if constexpr ( false )
{
std::cout << Empty{}.bar; // does not exist
}
*/
// now the code is in a lambda with auto param, so basicly a template method
[&](const auto& empty)
{
if constexpr ( false )
{
std::cout << empty.bar;
}
}(Empty{});
}
demo : https://wandbox.org/permlink/XEgZ6PXPLyyjXDlO
The fact I can use the [&] syntax make this solution way simpler to use than create an helper method since I don't need to forward all the parameters I need inside my if constexpr