Рассмотрим два struct
s с псевдонимами другого типа:
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{}; }
}
Увидеть Пост Ричарда Смита на std-обсуждение:
В реализации я знаком с [т.е. Clang], ключевая проблема заключается в том, что лексические области, используемые при обработке определения функции, являются в основном временными, что означает, что задержка создания экземпляра некоторой части определения шаблона функции трудно поддерживать. Общие лямбды не страдают от проблем здесь, потому что тело обобщенной лямбды создается с помощью шаблона функции включения, [..]
То есть общие тела лямбды частично создаются с использованием локального контекста (включая аргументы шаблона), когда создается экземпляр шаблона; Таким образом, в рамках реализации Clang, T::x
а также T::y
подставляются напрямую, поскольку тип замыкания может быть передан наружу. Это приводит к провалу. Как указывает @ T.C., Код может считаться некорректным, не требует диагностики, так как создание экземпляра s<foo>
дает определение шаблона (определение замыкания), чей второй if constexpr
Ветка не имеет четко сформированных экземпляров. Это объясняет поведение Clang и GCC.
Это сводится к архитектурной проблеме в основной реализации (см. Также этот ответ; GCC, очевидно, не страдает от этого ограничения), поэтому я был бы удивлен, если бы Core посчитал ваш код правильно сформированным (в конце концов, они учли это в дизайне общих лямбда-захватов — см. Связанный ответ). GCC, поддерживающий ваш код, в лучшем случае является функцией (но, вероятно, вредной, поскольку позволяет писать код, зависящий от реализации).
Других решений пока нет …