I have a problem with "if constexpr" in a templated lambda. For the sake of argument let's ignore how I got there, but I have a struct foo that is defined in some way to result in something as follows:
template<bool condition>
struct foo {
int a;
// Only contains b if condition is true
int b;
}
Now I can define a templated function thtemplate
template<bool condition>
void print_fun(foo & obj) {
/* Do something with obj.a */
if constexpr(condition)
/* Do something with obj.b */
};
Instantiating this function and using it will compile, if the constexpr parameter to foo
is the same as the one to print_fun
, i.e.
constexpr bool no = false;
foo<no> obj = {};
print_fun<no>(obj);
This does compile because the false branch is discarded inside a templated entity, and thus there is no problem with using obj.b inside print_fun.
However, if I define a similar lambda expression as follows:
template<bool condition>
auto print_lambda = [](foo & obj) {
/* Do something with obj.a */
if constexpr(condition)
/* Do something with obj.b */
};
and instantiate it:
constexpr bool no = false;
foo<no> obj = {};
print_lambda<no>(obj);
then the false branch is not discarded and the compiler gives me
'b': is not a member of 'foo'
Is this intended behavior, does it happen on other compilers? Am I doing something wrong? Or is it a bug in the compiler? (Microsoft Visual Studio Version 15.4.1, gcc 7.2)
Check out my test here with gcc, where it does not compile for a functor or function either.
Edit:
Here is the code of a my minimal example, I was not aware that the external link wouldn't suffice. This compiles on Visual Studio 15.4.1, except for the noted line.
foo_bar
takes the place of foo
in my description.
#include <iostream>
constexpr bool no = false;
struct foo {
int x;
};
struct bar {
int y;
};
template <bool, typename AlwaysTy, typename ConditionalTy>
struct Combined : AlwaysTy {};
template <typename AlwaysTy, typename ConditionalTy>
struct Combined<true, AlwaysTy, ConditionalTy> : AlwaysTy, ConditionalTy {};
using foo_bar = Combined<no, foo, bar>;
template<bool condition>
void print_fun(foo_bar & obj) {
std::cout << obj.x << std::endl;
if constexpr(condition)
std::cout << obj.y << std::endl;
};
template<bool condition>
auto print_lambda = [](foo_bar & obj) {
std::cout << obj.x << std::endl;
if constexpr(condition)
std::cout << obj.y << std::endl;
};
int main(int argc, char ** argv) {
foo_bar obj = {};
print_lambda<no>(obj); // Does not compile
print_fun<no>(obj);
}
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With