Использование аргумента функции как части постоянного выражения - gcc vs clang
Рассмотрим следующий фрагмент кода:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{})
{
}
-
clang++ (соединительная линия) компилирует код
-
g++ (trunk) завершает компиляцию со следующей ошибкой:
src:7:34: error: template argument 1 is invalid
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:34: error: template argument 1 is invalid
src:7:25: error: invalid template-id
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: class template argument deduction failed:
auto f(T t) -> decltype(B<pred(t)>{})
^
src:7:36: error: no matching function for call to 'B()'
src:1:24: note: candidate: 'template<bool <anonymous> > B()-> B<<anonymous> >'
template <bool> struct B { };
^
src:1:24: note: template argument deduction/substitution failed:
src:7:36: note: couldn't deduce template parameter '<anonymous>'
auto f(T t) -> decltype(B<pred(t)>{})
^
живой пример на godbolt.org
Хотя диагностика g++ вводит в заблуждение, я предполагаю, что проблема здесь в том, что t
не является постоянным выражением. Изменение кода на...
decltype(B<pred(T{})>{})
... исправляет ошибку компиляции на g++: живой пример на godbolt.org
Какой компилятор работает здесь правильно?
Ответы
Ответ 1
GCC ошибочен. Нет правила, которое предотвращает использование параметров функции в постоянном выражении таким образом.
Однако вы не можете использовать значение параметра в таком контексте, а набор типов T
для которых f
является вызываемым, довольно ограничен. Чтобы понять, почему, мы должны рассмотреть, какие конструкции будут оцениваться при оценке выражения pred(t)
:
// parameters renamed for clarity
template <typename U>
constexpr bool pred(U u) { return true; }
template <typename T>
auto f(T t) -> decltype(B<pred(t)>{});
Семантика оценки для вызова pred(t)
выглядит следующим образом:
- copy-initialize
pred
параметр u
из f
параметра t
- оценивать тело
pred
, которое тривиально создает значение bool
true
- уничтожить
u
Таким образом, f
допускается только для типов T
для которых приведенное выше включает только конструкции, которые действительны при постоянной оценке (см. [Expr.const] p2 для правил). Требования:
-
T
должен быть буквальным типом - копирование-инициализация
u
из t
должно быть постоянным выражением и, в частности, не должно выполнять преобразование lvalue-to-r на любом члене t
(потому что их значения неизвестны) и не должны указывать какой-либо ссылочный элемент t
На практике это означает, что f
является вызываемым, если T
является пустым типом класса с конструктором копии по умолчанию или если T
является типом класса, конструктор которого является constexpr
и не читает никаких членов его аргумента, или (как ни странно), если T
is std::nullptr_t
(хотя clang в настоящее время получает случай nullptr_t
неправильно).
Ответ 2
Компилятор ожидает параметр в этом контексте, потому что ему необходимо оценить полный (шаблонный) тип функции. Учитывая реализацию pred, любое значение будет работать в этом месте. Здесь он привязывает тип шаблона параметра f к аргументу. Компилятор g++, по-видимому, делает упрощающее предположение о том, что функция шаблона constexpr
каким-то образом будет изменена любыми параметрами, если они не являются также const
, которые, как вы показали, и clang согласны, не обязательно так.
Все сводится к тому, насколько глубоко внутри реализации функции компилятор идет, чтобы отметить функцию как неконстантную из-за неконстантного вклада в возвращаемое значение.
Затем возникает вопрос о том, была ли эта функция создана и требует, чтобы компилятор фактически скомпилировал код и выполнил синтаксический анализ шаблона, который, по крайней мере, с g++, представляется другим уровнем компиляции.
Затем я пошел на стандарт, и они любезно позволяют автору компилятора сделать именно это упрощающее предположение, а создание экземпляра шаблона должно работать только для f<const T>
или f <const T&>
constexpr 'должны иметь: каждый из его параметров должен быть LiteralType
Таким образом, код шаблона должен компилироваться, но сбой, если он создается с помощью неконстантного T.
Ответ 3
t
не является значением constexpr, это значение pred(t)
не является constexpr. Вы не можете использовать его в B<pred(t)>
потому что для этого требуется constexpr.
Эта версия правильно компилируется:
template <bool> struct B { };
template <typename T>
constexpr bool pred(T t) { return true; }
template <typename T, T t>
auto f() -> decltype(B<pred(t)>{})
{
}
https://godbolt.org/g/ydbj1X
Другой действующий код:
template <typename T>
auto f(T t) -> decltype(pred(t))
{
}
Это связано с тем, что вы не оцениваете pred(t)
только вы получаете информацию о типе. B<pred(t)>
нужна оценка pred(t)
иначе вы получите B<true>
или B<false>
, для любого нормального значения вы не сможете этого сделать.
std::integral_constant<int, 0>{}
может работать в случае Клана, вероятно, потому, что его значение встраивается как часть типа и всегда одинаково. Если мы немного изменим код:
template <typename T>
auto f(T t) -> decltype(B<pred(decltype(t){})>{})
{
return {};
}
и Clang, и GCC компилируют его, в случае std::integral_constant
decltype(t){}
, и t
и decltype(t){}
всегда имеют одинаковое значение.