Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

False-branch of if constexpr not discarded in templated lambda

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);
}
like image 218
Hanno Bänsch Avatar asked Oct 29 '17 13:10

Hanno Bänsch


1 Answers

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

like image 55
songyuanyao Avatar answered Oct 21 '22 15:10

songyuanyao