Специальное поведение для оператора вызова типа 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() Операнды аргумента явно зависят от типа.

Другие вопросы по тегам