Ответ 1
Поскольку при наличии параметра шаблона decltype
возвращает тип зависимости, зависящий от типа, в соответствии со стандартом, см. ниже. Если параметр шаблона отсутствует, он разрешает очевидный size_t
. Таким образом, в этом случае вы должны выбрать либо объявление, либо определение имеют независимое выражение (например, size_t/decltype(sizeof(int))
), как возвращаемый тип, или оба имеют зависимое выражение (например, decltype(sizeof(T))
), которое разрешено к уникальному зависимому типу и рассматривается эквивалентны, если их выражения эквивалентны (см. ниже).
В этом сообщении я использую стандартный проект С++ N3337.
§ 7.1.6.2 [dcl.type.simpl]
¶ 4
Тип, обозначенный как decltype (e), определяется следующим образом: - если e является несферированным id-выражением или несвязанным доступом к члену класса (5.2.5), decltype (e) является типом объекта, названного e. Если такой объект отсутствует или если e называет набор перегруженных функций, программа плохо организована,
- в противном случае, если e является значением x, decltype (e) является T & &, где T - тип e;
- в противном случае, если e является lvalue, decltype (e) является T &, где T - тип e;
- в противном случае decltype (e) - тип e.
Это объясняет, что такое decltype(sizeof(int))
. Но для decltype(sizeof(T))
есть другой раздел, объясняющий, что это такое.
§ 14.4 [temp.type]
¶ 2
Если выражение e включает параметр шаблона, decltype (e) обозначает уникальный зависимый тип. Два таких Спецификаторы decltype относятся к одному типу, только если их выражения эквивалентны (14.5.6.1). [Примечание: однако, он может быть псевдонимом, например, с помощью typedef-name. - конечная нота]
В источниках Clang LLVM версии 3.9 в файле lib/AST/Type.cpp
DecltypeType::DecltypeType(Expr *E, QualType underlyingType, QualType can)
// C++11 [temp.type]p2: "If an expression e involves a template parameter,
// decltype(e) denotes a unique dependent type." Hence a decltype type is
// type-dependent even if its expression is only instantiation-dependent.
: Type(Decltype, can, E->isInstantiationDependent(),
E->isInstantiationDependent(),
E->getType()->isVariablyModifiedType(),
E->containsUnexpandedParameterPack()),
Важная фраза начинается с "Следовательно, decltype...". Он снова разъясняет ситуацию.
Снова в источниках Clang версии 3.9 в файле lib/AST/ASTContext.cpp
QualType ASTContext::getDecltypeType(Expr *e, QualType UnderlyingType) const {
DecltypeType *dt;
// C++11 [temp.type]p2:
// If an expression e involves a template parameter, decltype(e) denotes a
// unique dependent type. Two such decltype-specifiers refer to the same
// type only if their expressions are equivalent (14.5.6.1).
if (e->isInstantiationDependent()) {
llvm::FoldingSetNodeID ID;
DependentDecltypeType::Profile(ID, *this, e);
void *InsertPos = nullptr;
DependentDecltypeType *Canon
= DependentDecltypeTypes.FindNodeOrInsertPos(ID, InsertPos);
if (!Canon) {
// Build a new, canonical typeof(expr) type.
Canon = new (*this, TypeAlignment) DependentDecltypeType(*this, e);
DependentDecltypeTypes.InsertNode(Canon, InsertPos);
}
dt = new (*this, TypeAlignment)
DecltypeType(e, UnderlyingType, QualType((DecltypeType *)Canon, 0));
} else {
dt = new (*this, TypeAlignment)
DecltypeType(e, UnderlyingType, getCanonicalType(UnderlyingType));
}
Types.push_back(dt);
return QualType(dt, 0);
}
Итак, вы видите, что Clang собирает и выбирает эти уникальные зависимые типы decltype
в/из специального набора.
Почему компилятор настолько глуп, что не видит, что выражение decltype
равно sizeof(T)
, которое всегда size_t
? Да, это очевидно для человека-читателя. Но когда вы разрабатываете и реализуете формальные грамматические и семантические правила, особенно для таких сложных языков, как С++, вам приходится группировать проблемы и определять правила для них, а не просто придумывать правило для каждой конкретной проблемы, в последнем вы просто не сможете переместиться с вашим языком/компилятором. То же самое здесь нет справедливого правила: если decltype
имеет выражение вызова функции, которое не требует разрешения параметров шаблона - разрешите decltype
на возвращаемый тип функции. Существует более того, есть так много случаев, которые вам нужно уделить, что вы придумали более общее правило, например, приведенное выше из стандарта (14.4[2]
).
Кроме того, аналогичный неочевидный случай с auto
, decltype(auto)
, найденный AndyG в С++ - 14 (N4296, § 7.1.6.4 [dcl.spec.auto] 12/13):
§ 7.1.6.4 [dcl.spec.auto]
¶ 13
Редекларации или специализации шаблона функции или функции с объявленным типом возврата, который использует тип заполнителя также должен использовать этот заполнитель, а не выведенный тип. [Пример:
auto f(); auto f() { return 42; } // return type is int auto f(); // OK int f(); // error, cannot be overloaded with auto f() decltype(auto) f(); // error, auto and decltype(auto) don’t match
Изменения в С++ 17, номер документa >= N4582
Изменение стандартного черновика N4582 с марта 2016 года (благодаря bogdan) обобщает утверждение:
§ 17.4 (старый § 14.4) [temp.type]
¶ 2
Если выражение e зависит от типа (17.6.2.2), decltype (e) обозначает уникальный зависимый тип. Два таких Спецификаторы decltype относятся к одному типу, только если их выражения эквивалентны (17.5.6.1). [Примечание: однако, такой тип может быть псевдонимом, например, typedef-name. - конечная нота]
Это изменение приводит к другому разделу, описывающему зависимое от типа выражение, которое выглядит довольно странным для нашего конкретного случая.
§ 17.6.2.2 [temp.dep.expr] (старый § 14.6.2.2)
¶ 4
Выражения следующих форм никогда не зависят от типа (поскольку тип выражения не может быть зависимый):
... sizeof ( type-id ) ...
Существуют дополнительные разделы зависимых от стоимости выражений, где sizeof
может быть зависящим от значения, если зависимо от type-id
. Связь между зависимым от стоимости выражением и decltype
отсутствует. После некоторого раздумья я не нашел причин, почему decltype(sizeof(T))
не должен или не может разрешить в size_t
. И я бы предположил, что это было довольно подлое изменение ( "включает параметр шаблона" в "зависящий от типа" ) в стандарте, который разработчики компилятора не обращали особого внимания (возможно, перегружены многими другими изменениями, возможно, не думали, что это может на самом деле что-то изменить, просто упростить формулировку). Изменение имеет смысл, поскольку sizeof(T)
не зависит от типа, он зависит от стоимости. Оператор decltype(e)
- неоцениваемый операнд, т.е. не заботится о значении, только о типе. Поэтому decltype
возвращает уникальный тип только тогда, когда e
зависит от типа. sizeof(e)
может быть только зависимым от значения.
Я пробовал код с clang 5, gcc 8 -std=c++1z
- тот же результат: ошибка. Я пошел дальше и пробовал этот код:
template <typename>
struct Cls {
static std::size_t f();
};
template <typename T>
decltype(sizeof(sizeof(T))) Cls<T>::f() {
return 0;
}
Произошла та же ошибка, даже если sizeof(sizeof(T))
не зависит от типа или значения (см. этот пост). Это дает мне основание предполагать, что компиляторы работают по-старому стандарту С++ - 11/14 (т.е. "Включает параметр шаблона" ), как в исходном фрагменте выше, из источника clang 3.9 (я могу проверить, что последняя разработка clang 5.0 имеет одинаковые строки, не нашел ничего связанного с этим новым изменением в стандарте), но не зависит от типа.