Подтвердить что ты не робот

Доступ к типу члена с `if constexpr` внутри общей лямбды требует, чтобы обе ветки были хорошо сформированы - gcc vs clang

Рассмотрим два struct с разными псевдонимами типа элемента:

struct foo { using x = int;   };
struct bar { using y = float; };

Учитывая T в контексте template, я хочу получить либо T::x, либо T::y в зависимости от того, что T:

template <typename T>
auto s()
{
    auto l = [](auto p) 
    {
        if constexpr(p) { return typename T::x{}; }
        else            { return typename T::y{}; }
    };

    return l(std::is_same<T, foo>{});
}

int main() 
{ 
    s<foo>(); 
}

g++ компилирует код выше, а clang++ вызывает эту ошибку:

error: no type named 'y' in 'foo'
        else            { return typename T::y{}; }
                                 ~~~~~~~~~~~~^
note: in instantiation of function template specialization 's<foo>' requested here
    s<foo>();
    ^

на godbolt.org, с просмотром соответствия


Неправильно ли отклоняется clang++ этот код?

Обратите внимание, что clang++ принимает код при удалении косвенности через общий лямбда l:

template <typename T>
auto s()
{
    if constexpr(std::is_same<T, foo>{}) { return typename T::x{}; }
    else                                 { return typename T::y{}; }
}
4b9b3361

Ответ 1

См. пост Ричарда Смита на std-обсуждении:

В реализации я знаком с [т. Clang], ключевая проблема заключается в том, что лексические области, используемые при обработке определения функции, в корне преходящи, что означает, что отсрочить создание некоторой части определения шаблона функции трудно. Родовые лямбды не страдают от проблемы здесь, потому что тело общей лямбды создается с помощью шаблона закрывающей функции, [..]

То есть, общие тела lambdas частично создаются с использованием локального контекста (включая аргументы шаблона) при создании экземпляра шаблона; таким образом, при реализации Clang, T::x и T::y подставляются непосредственно, так как тип закрытия может быть передан за пределы. Это приводит к сбою. Как указано в [T.C.], Код можно считать плохо сформированным, не требуется диагностика, поскольку в экземпляре s<foo> дается определение шаблона (закрытие), вторая ветвь if constexpr не имеет корректных экземпляров. Это объясняет поведение как Clang, так и GCC.

Это сводится к архитектурной проблеме в крупной реализации (см. также этот ответ; GCC, по-видимому, не страдает от этого ограничения), поэтому я был бы удивил, если Core сочтет ваш код хорошо сформированным (в конце концов, они учли это при разработке общих лямбда-захватов - см. связанный ответ). GCC, поддерживающий ваш код, в лучшем случае является функцией (но, вероятно, вредной, так как позволяет писать код, зависящий от реализации).