When using decltype,
is it permissible to use the traditional leading return type syntax in a
declaration:
decltype(expr) foo();
and then use C++11 trailing return type syntax in the definition?
auto foo() -> decltype(expr) { /*...*/ }
I think the answer is yes, as C++11 8.3.5p1 (leading return type) and
8.3.5p2 (trailing return type) both seem to yield the same final type
description regardless of which side the return type appears on, and
7.1.6.2p4 (decltype) doesn't seem to have anything that would
change that. Furthermore, the note in 9.3p9 shows an example of
declaring a member using a typedef, but explains it could not be defined
with a typedef, implying that they do not have to use exactly the same
syntactic conventions.
However, I've got an example where both Clang and GCC do not think it is allowed, although MSVC does (godbolt link):
// The class must be a template for the problem to happen.
template <typename T>
struct Class {
// This has to be inside the class for the problem to happen.
int dataMember;
// All is fine if I use trailing return type here.
//auto method() -> decltype(dataMember);
// But it fails with leading return type in the declaration.
decltype(dataMember) method();
};
// Definition uses trailing return type.
template <typename T>
auto Class<T>::method() -> decltype(dataMember) {
return 3;
}
Clang 16.0.0 says:
<source>:16:16: error: return type of out-of-line definition of 'Class::method' differs from that in the declaration
auto Class<T>::method() -> decltype(dataMember) {
^
<source>:11:24: note: previous declaration is here
decltype(dataMember) method();
~~~~~~~~~~~~~~~~~~~~ ^
1 error generated.
Compiler returned: 1
GCC 13.1 says, somewhat more informatively but dubiously:
<source>:16:6: error: no declaration matches 'decltype (((Class<T>*)this)->Class<T>::dataMember) Class<T>::method()'
16 | auto Class<T>::method() -> decltype(dataMember) {
| ^~~~~~~~
<source>:11:24: note: candidate is: 'decltype (Class<T>::dataMember) Class<T>::method()'
11 | decltype(dataMember) method();
| ^~~~~~
<source>:3:8: note: 'struct Class<T>' defined here
3 | struct Class {
| ^~~~~
Compiler returned: 1
MSVC 19 is happy with this code, even if I add code that instantiates
Class and calls method (although it then issues a warning about
electing to inline the method, which is a little weird).
I'm inclined to believe that these two signatures (one with leading
return type and one with trailing return type) are in fact supposed to
be equivalent in C++, and that furthermore I am allowed to use leading
return type in the declaration and trailing return type in the
definition, despite these two compilers' objections, because for both of
them, the problem only happens if the class is a template and
dataMember is declared inside it (suggesting the compilers might both
have similar bugs).
Have I overlooked some provision that makes this not permissible?
(Tangent: Why would I want the declaration and definition to differ in this regard? Normally I would not, but this is the output of a code transformation tool. The declaration is in the original code, which usually uses leading return type, and which I want to only minimally change, while the definition is generated, and the trailing return type syntax is very convenient in that context due to all class members being in scope there.)
Clarification: In the example above, dataMember has type int (because I wanted it to be minimal). But this evidently causes the answer to depend on CWG2064 because it is not a dependent type. My intended focus is the general case, including dependent types. So, the ideal answer would instead primarily answer for the case where int has been replaced with T (godbolt), with the degenerate case of int treated separately (if necessary).
Neither gcc nor clang implement CWG2064.
This means since
dataMember"involves a template parameter" (what clang calls "instantiation-dependent") by virtue of being a member of the current-instantiation,decltype(dataMember)is a "unique dependent type".[temp.type]p4 (without wording changed by DR2064):
[temp.over.link]p5:
[class.mfct.non.static]p2:
So at your declaration
decltype(dataMember) method();,dataMemberrefers to the same thing asClass::dataMemberbecause there is nothis(therefore there is no current class), but at your definitiontemplate <typename T> auto Class<T>::method() -> decltype(dataMember),dataMemberis(*this).dataMember.Even though they have the same tokens, these expressions are not equivalent because of the ODR [basic.def.odr]p(14.5):
(
(*this).dataMemberis not the same entity asClass<T>::dataMember)You can see this is the case if you make sure there is no
(*this).transformation:If gcc and clang were to implement CWG2064,
decltype(dataMember)would no longer be a dependent type and would simply beint, so this would work.You would run into the same issue if you were to make the type of
dataMemberdependent: