I've been playing with deduced return types in definitions that resolve to the same type as the declaration. This works:
template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(int)) Cls<T>::f() { return 0; }
But if I change the definition to something that should be equivalent by replacing sizeof(int)
with sizeof(T)
it fails
template <typename T> decltype(sizeof(T)) Cls<T>::f() { return 0; }
gcc's error (clang is almost identical):
error: prototype for ‘decltype (sizeof (T)) Cls<T>::f()’ does not match any in class ‘Cls<T>’ decltype(sizeof(T)) Cls<T>::f() { return 0; } ^~~~~~ so.cpp:4:24: error: candidate is: static std::size_t Cls<T>::f() static std::size_t f(); ^
The same problem arises with function parameter types:
template <typename> struct Cls { static void f(std::size_t); }; template <typename T> void Cls<T>::f(decltype(sizeof(T))) { } // sizeof(int) works instead
Stranger yet, if the declaration and definition match and both use decltype(sizeof(T))
it compiles successfully, and I can static_assert
that the return type is size_t
. The following compiles successfully:
#include <type_traits> template <typename T> struct Cls { static decltype(sizeof(T)) f(); }; template <typename T> decltype(sizeof(T)) Cls<T>::f() { return 0; } static_assert(std::is_same<std::size_t, decltype(Cls<int>::f())>{}, "");
Update with another example. This isn't a dependent type but still fails.
template <int I> struct Cls { static int f(); }; template <int I> decltype(I) Cls<I>::f() { return I; }
If I use decltype(I)
in both the definition and declaration it works, if I use int
in both the definition and declaration it works, but having the two differ fails.
Update 2: A similar example. If Cls
is changed to not be a class template, it compiles successfully.
template <typename> struct Cls { static int f(); using Integer = decltype(Cls::f()); }; template <typename T> typename Cls<T>::Integer Cls<T>::f() { return I; }
Update 3: Another failing example from M.M. A templated member function of a non-templated class.
struct S { template <int N> int f(); }; template <int N> decltype(N) S::f() {}
Why is it illegal for the declaration and definition to disagree with a dependent type only? Why is it affected even when the type itself isn't dependent as with the template <int I>
above?
The decltype type specifier yields the type of a specified expression. The decltype type specifier, together with the auto keyword, is useful primarily to developers who write template libraries. Use auto and decltype to declare a template function whose return type depends on the types of its template arguments.
Decltype keyword in C++ Decltype stands for declared type of an entity or the type of an expression. It lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression. SYNTAX : decltype( expression )
decltype(auto) is primarily useful for deducing the return type of forwarding functions and similar wrappers, where you want the type to exactly “track” some expression you're invoking.
decltype returnsIf what we pass to decltype is the name of a variable (e.g. decltype(x) above) or function or denotes a member of an object ( decltype x.i ), then the result is the type of whatever this refers to. As the example of decltype(y) above shows, this includes reference, const and volatile specifiers.
Because when there is a template parameter involved, decltype
returns an unqiue dependent type according to the standard, see below. If there is no template parameter then it resolves to an obvious size_t
. So in this case you have to choose either both declaration and definition have an independent expression (e.g. size_t/decltype(sizeof(int))
), as a return type, or both have dependent expression (e.g. decltype(sizeof(T))
), which resolved to an unique dependent type and considered to be equivalent, if their expressions are equivalent (see below).
In this post I am using the C++ standard draft N3337.
§ 7.1.6.2 [dcl.type.simpl]
¶ 4
The type denoted by decltype(e) is defined as follows: — if e is an unparenthesized id-expression or an unparenthesized class member access (5.2.5), decltype(e) is the type of the entity named by e. If there is no such entity, or if e names a set of overloaded func- tions, the program is ill-formed;
— otherwise, if e is an xvalue, decltype(e) is T&&, where T is the type of e;
— otherwise, if e is an lvalue, decltype(e) is T&, where T is the type of e;
— otherwise, decltype(e) is the type of e.
This explains what is decltype(sizeof(int))
. But for decltype(sizeof(T))
there is another section explaining what it is.
§ 14.4 [temp.type]
¶ 2
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). [ Note: however, it may be aliased, e.g., by a typedef-name. — end note ]
In Clang LLVM sources version 3.9 in file 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()),
The important phrase starts as "Hence a decltype...". It again clarifies the situation.
Again in Clang sources version 3.9 in file 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); }
So you see Clang gathers and picks those unique dependent types of decltype
in/from a special set.
Why compiler is so stupid that it does not see that the expression of decltype
is sizeof(T)
that is always size_t
? Yes, this is obvious to a human reader. But when you design and implement a formal grammar and semantic rules, especially for such complicated languages as C++, you have to group problems up and define the rules for them, rather than just come up with a rule for each particular problem, in the latter way you just wont be able to move with your language/compiler design. The same here there is no just rule: if decltype
has a function call expression that does not need any template parameters resolution - resolve decltype
to the return type of the function. There is more than that, there are so many cases you need to cover, that you come up with a more generic rule, like the quoted above from the standard (14.4[2]
).
In addition, a similar non-obvious case with auto
, decltype(auto)
found by AndyG in C++-14 (N4296, § 7.1.6.4 [dcl.spec.auto] 12/13):
§ 7.1.6.4 [dcl.spec.auto]
¶ 13
Redeclarations or specializations of a function or function template with a declared return type that uses a placeholder type shall also use that placeholder, not a deduced type. [ Example:
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
Change in the standard draft N4582 from March 2016 (thanks to bogdan) generalizes the statement:
§ 17.4 (old § 14.4) [temp.type]
¶ 2
If an expression e is type-dependent (17.6.2.2), decltype(e) denotes a unique dependent type. Two such decltype-specifiers refer to the same type only if their expressions are equivalent (17.5.6.1). [ Note: however, such a type may be aliased, e.g., by a typedef-name. — end note ]
This change leads to another section describing the type dependent expression that looks quite strange to our particular case.
§ 17.6.2.2 [temp.dep.expr] (old § 14.6.2.2)
¶ 4
Expressions of the following forms are never type-dependent (because the type of the expression cannot be dependent):
... sizeof ( type-id ) ...
There are further sections on value-dependent expressions where sizeof
can be value-dependent if the type-id
dependent. There is no relation between value-dependent expression and decltype
. After some thinking, I did not find any reason why decltype(sizeof(T))
must not or cannot resolve into size_t
. And I would assume that was quite sneaky change ("involves a template parameter" to "type-dependent") in the standard that compiler developers did not pay much attention to (maybe overwhelmed by many other changes, maybe did not think that it might actually change something, just a simple formulation improvement). The change does make sense, because sizeof(T)
is not type-dependent, it is value-dependent. decltype(e)
's operand is a unevaluated operand, i.e. does not care about value, only about type. That is why decltype
returns a unique type only when e
is type-dependent. sizeof(e)
might be only value-dependent.
I tried the code with clang 5, gcc 8 -std=c++1z
- the same result: error. I went further and tried this code:
template <typename> struct Cls { static std::size_t f(); }; template <typename T> decltype(sizeof(sizeof(T))) Cls<T>::f() { return 0; }
The same error was given, even that sizeof(sizeof(T))
is neither type- or value-dependent (see this post). This gives me a reason to assume that the compilers are working in an old way of C++-11/14 standard (i.e. "involves a template parameter") like in the source snippet above from clang 3.9 source (I can verify that the latest developing clang 5.0 has the same lines, have not found anything related to this new change in the standard), but not type-dependent.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With