Ложная ветвь if constexpr не отбрасывается в шаблонных лямбда

У меня проблема с "if constexpr" в шаблонной лямбда. Для аргумента пусть игнорирует, как я туда попал, но у меня есть структура foo, которая каким-то образом определяется следующим образом:

template<bool condition>
struct foo {
    int a;

    // Only contains b if condition is true
    int b;
}

Теперь я могу определить шаблонную функцию thtemplate

template<bool condition>
void print_fun(foo & obj) {
    /* Do something with obj.a */
    if constexpr(condition)
        /* Do something with obj.b */
};

Игнорирование этой функции и ее использование скомпилируется, если параметр constexpr в foo совпадает с параметром print_fun, т.е.

constexpr bool no = false;
foo<no> obj = {};
print_fun<no>(obj);

Это компилируется, потому что ложная ветвь отбрасывается внутри шаблонизированного объекта и, следовательно, нет проблем с использованием obj.b внутри print_fun.

Однако, если я определяю аналогичное лямбда-выражение следующим образом:

template<bool condition>
auto print_lambda = [](foo & obj) {
    /* Do something with obj.a */
    if constexpr(condition)
        /* Do something with obj.b */
};

и создайте экземпляр:

constexpr bool no = false;
foo<no> obj = {};
print_lambda<no>(obj);

то ложная ветвь не отбрасывается, и компилятор дает мне

'b': не является членом 'foo'

Является ли это предполагаемым поведением, это происходит на других компиляторах? Я делаю что-то неправильно? Или это ошибка в компиляторе? (Microsoft Visual Studio версии 15.4.1, gcc 7.2)

Проверьте мой тест здесь с gcc, где он не компилируется для функтора или функции.

Изменить: Вот код моего минимального примера, я не знал, что внешней ссылки не хватит. Это компилируется на Visual Studio 15.4.1, за исключением отмеченной строки. foo_bar заменяет foo в моем описании.

#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);
}

Ответ 1

В соответствии с связанным кодом

template<bool condition>
void print_fun(foo_bar & obj) {
    std::cout << obj.x << std::endl;
    if constexpr(condition)
        std::cout << obj.y << std::endl;
}

Проблема заключается в использовании if constexpr, утверждение std::cout << obj.y << std::endl; плохо сформировано для всех возможных экземпляров шаблона print_fun; то есть независимо от того, какое значение condition оно просто плохо сформировалось.

Примечание. Отброшенная инструкция не может быть плохо сформирована для каждой возможной специализации:

Общим обходным путем для такого оператора catch-all является зависимое от типа выражение, которое всегда ложно:

Чтобы исправить это, вы можете сделать оператор зависимым от параметра шаблона, например.

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;
}

и использовать его как

foo_bar<no> obj = {};
print_fun<no>(obj);

Теперь для obj.y, obj имеет тип foo_bar<condition>, который зависит от параметра шаблона condition.

LIVE