Преобразование из базы данных в неполные типы, необходимые для decltype - PullRequest
12 голосов
/ 26 апреля 2019

Я наткнулся на этот фрагмент кода, включающий конечные типы возврата и наследование.

Следующий минимальный пример прекрасно компилируется с g ++, а не clang

struct Base {};

int foo(Base&) {
    return 42;
}

struct Derived : public Base {
    auto bar() -> decltype(foo(*this)) {
        return foo(*this);
    }
};

int main()
{
    Derived derived;
    derived.bar();  

    return 0;
}

Однако, если мы изменим auto bar() -> decltype(foo(*this)) на decltype(auto) bar() (расширение c ++ 14), код также скомпилируется с помощью clang. Ссылка на Годболт https://godbolt.org/z/qf_k6X.

Может кто-нибудь объяснить мне

  • чем auto bar() -> decltype(return expression) отличается от decltype(auto) bar()
  • почему поведение между компиляторами отличается
  • что такое правильная реализация?

Ответы [ 2 ]

5 голосов
/ 26 апреля 2019

Это ошибка gcc, конечный тип возврата не находится в контексте полного класса [class.mem]

Завершено-класс класса класса представляет собой

  • тело функции,
  • аргумент по умолчанию,
  • noexcept-спецификатор ([кроме.spec]),
  • условие контракта или
  • инициализатор члена по умолчанию

Мы видим, что для преобразования из базового в базовый класс из [conv.ptr] необходим полный класс

Значение типа «указатель на cv D», где D - это полный тип класса , может быть преобразовано в значение типа «указатель на cv B».”, Где B является базовым классом D.

и [dcl.init.ref]

« cv1 T1 »является справочнымсовместим с «cv2 T2», если значение типа «указатель на cv2 T2» можно преобразовать в тип «указатель на cv1 T1» с помощью стандартной последовательности преобразования.Во всех случаях, когда эталонно-совместимые отношения двух типов используются для установления достоверности эталонной привязки, а стандартная последовательность преобразования будет некорректной, программа, для которой требуется такая привязка, неверна.

С другой стороны, тело функции находится в пределах контекста полного класса , и, следовательно, преобразование в базовое преобразование корректно сформировано.Тип возврата, включающий тип заполнителя (decltype(auto)) , действителен , если он уже выведен до выражения, использующего его.

Для возможного обходного пути в C ++ 11 выможет использовать

auto bar() -> decltype(foo(std::declval<Base&>()))
{
    return foo(*this);
}

, если вы знаете, что хотите позвонить по номеру Base.

1 голос
/ 26 апреля 2019

Я думаю, что Clang не прав, отвергая это:

Относительно типа возврата определения функции, стандарт C ++ 14 гласит:

[dcl.fct]/9]

Типы не должны быть определены в типах возвращаемых значений или параметров. Тип параметра или тип возвращаемого значения для определение функции не должно быть неполным типом класса (возможно, квалифицированным по cv), если только функция не удалена (8.4.3) или определение не вложено в спецификацию члена для этого класса (включая определения во вложенных классах, определенных внутри класса).

В вашем примере определение bar вложено в спецификацию члена class Derived. Так что это разрешено, и GCC, ICC и MSVC получают это право.

С другой стороны, decltype(auto) работает, потому что выведенный тип возврата фактически не выводится, пока не потребуется подпись функции. И в вашем случае это происходит, когда вы звоните bar() в main. В этот момент class Derived является полностью определенным типом. Clang получает это право.

Обратите внимание, что даже использование auto вместо decltype(auto) будет работать для вашего примера. См. Демо на Годболт.

...