Специальное поведение для оператора вызова типа decltype для неполных типов
Я боролся с проблемой компиляции и смог сократить проблему до небольшого сегмента кода.
Чтобы установить сцену, я пытаюсь сделать CRTP, где базовый метод вызывает другой в производном классе. Сложность в том, что я хочу использовать конечные типы возврата, чтобы получить тип пересылки непосредственно в метод класса Derived. Это всегда не удается скомпилировать, если я не переадресую к оператору вызова в производном классе.
Это компилирует:
#include <utility>
struct Incomplete;
template <typename Blah>
struct Base
{
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Blah&>()(std::declval<Args&&>()...));
};
void example()
{
Base<Incomplete> derived;
}
Пока это не так: (обратите внимание на комментарий для единственной разницы)
#include <utility>
struct Incomplete;
template <typename Blah>
struct Base
{
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...));
// I only added this ^^^^^^^^^^^
};
void example()
{
Base<Incomplete> derived;
}
Ошибка, которую я получаю:
<source>: In instantiation of 'struct Base<Incomplete>':
15 : <source>:15:22: required from here
10 : <source>:10:58: error: invalid use of incomplete type 'struct Incomplete'
-> decltype(std::declval<Blah&>().operator()(std::declval<Args&&>()...));
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
Кажется, во время разрешения decltype в классе Derived происходит какое-то особенное поведение. Есть ли что-то в стандарте, что бы объяснить это?
РЕДАКТИРОВАТЬ: сделал еще большее упрощение
PS: пример компиляции на Годболт: https://godbolt.org/g/St2gYC
1 ответ
Создание шаблона класса создает объявления шаблонов его функций-членов ( [temp.inst] / 2). Т.е. мы смотрим на декларацию
template <typename... Args>
auto entry(Args&&... args)
-> decltype(std::declval<Incomplete&>().operator()(std::declval<Args&&>()...));
Теперь рассмотрим [temp.res] / 10:
Если имя не зависит от параметра-шаблона (как определено в 14.6.2), объявление (или набор объявлений) для этого имени должно находиться в области действия в точке, где имя появляется в определении шаблона;
И действительно, имя operator()
не зависит от параметра шаблона. Это не зависит ни от типа, ни от значения, и также не является зависимым именем. Понятно, что в области действия нет декларации, следовательно, декларация неверна, диагностика не требуется.
Напротив, ваш первый фрагмент не требует поиска имени внутри Incomplete
, Преобразование x(...)
, где x
имеет тип класса, чтобы x.operator()(...)
происходит только после operator()
ищется в пределах x— [over.call]:
Таким образом, вызов
x(arg1,...)
интерпретируется как x.operator()(arg1, ...) для объекта класса x типа T, еслиT::operator()(T1, T2, T3)
существует и если оператор выбран в качестве функции наилучшего соответствия механизмом разрешения перегрузки ([over.match.best]).
Это отличается от абзаца, который сделал ваш второй код некорректным: [temp.res]/10 говорит, что некоторые объявления должны находиться в области видимости, и что имя связано с этими объявлениями. Вышеуказанное преобразование требует, чтобы типы аргументов (а также число...) были известны так, чтобы мы могли однозначно определить один operator()
быть названным; то есть мы не просто вставляем .operator()
, но всегда одновременно определяйте, какая операторная функция вызывается. Мы можем найти дальнейшее подтверждение этой интерпретации в [temp.dep]:
Если операнд оператора является зависимым от типа выражением, оператор также обозначает зависимое имя. Такие имена не связаны и ищутся в момент создания шаблона [...]
operator()
Операнды аргумента явно зависят от типа.