Специальное поведение decltype оператора вызова для неполных типов

Я боролся с проблемой компиляции и смог сократить проблему до небольшого сегмента кода.

Чтобы подготовить почву, я пытаюсь выполнить CRTP, где базовый метод вызывает другой в производном классе. Сложность в том, что я хочу использовать конечные возвращаемые типы, чтобы получить тип пересылки непосредственно в метод производного класса. Это всегда не удается скомпилировать , если я не пересылаю вызов оператору вызова в производном классе.

Это компилирует:

#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: пример компиляции на godbolt: https://godbolt.org/g/St2gYC


person Alexander Kondratskiy    schedule 05.08.2017    source источник
comment
Почему ты не пишешь std::declval<Blah>()? Я предполагаю, что второй блок - это случайное использование Incomplete, а первый - нет.   -  person Passer By    schedule 05.08.2017
comment
@PasserBy В любом случае это та же проблема. Я изменил его на ваше предложение, чтобы не было путаницы.   -  person Alexander Kondratskiy    schedule 05.08.2017
comment
Вы, может быть, ищете это? stackoverflow.com/questions/7943525/   -  person Amir Kirsh    schedule 07.08.2017


Ответы (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() явно зависят от типа.

person Columbo    schedule 07.08.2017
comment
Потрясающий! Прекрасное объяснение. В качестве примечания, способ, которым я обошел эту проблему, - заставить operator() зависеть от параметра-шаблона. Сначала введите еще одно объявление template <typename T, typename...> T mydeclval();, а затем используйте его так decltype(mydeclval<Incomplete, Args...>().operator(std::declval<Args&&>()...)) - person Alexander Kondratskiy; 07.08.2017