I have tried to construct a case that requires no typename or template, but still yield a variable or template depending on whether a given name t
is a function parameter pack or not
template<typename T> struct A { template<int> static void f(int) { } };
template<typename...T> struct A<void(T...,...)> { static const int f = 0; };
template<typename> using type = int;
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1);
}
The above will refer to the static const int
, and do a comparison. The following just has T t
changed to be a pack and make f
refer to a template, but GCC does not like either
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
int main() {
f(1, 2, 3);
}
GCC complains for the first
main.cpp:5:68: error: incomplete type 'A<void(type<decltype (t)>, ...)>' used in nested name specifier
template<typename T> void f(T t) { A<void(type<decltype(t)>...)>::f<0>(1); }
And for the second
main.cpp:5:74: error: invalid operands of types '<unresolved overloaded function type>' and 'int' to binary 'operator<'
template<typename ...T> void f(T ...t) { A<void(type<decltype(t)>...)>::f<0>(1); }
I have multiple questions
If I remove the body of the primary template, then for the f(1, 2, 3)
case, Clang complains
main.cpp:5:42: error: implicit instantiation of undefined template 'A<void (int)>'
Please note that it says A<void (int) >
, while I would expected A<void (int, int, int)>
. How does this behavior occur? Is this a bug in my code - i.e is it illformed, or is it a bug in Clang? I seem to remember a defect report about the order of expansion vs the substitution of alias template, is that relevant and does it render my code ill-formed?
Expanding a parameter pack either should, or does, make an expression type dependent. Regardless of whether the things expanded are type dependent.
If it did not, there would be a gaping hole in the type dependency rules of C++ and it would be a defect in the standard.
So A<void(type<decltype(t)>...)>::f
when t
is a pack, no matter what tricks you pull in the void(
here )
parts to unpack the t
, should be a dependent type, and template
is required before the f
if it is a template
.
In the case where t
is not a pack, it is intended that type<decltype(t)>
not be dependent (See http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390), but the standard may or may not agree at this point (I think not?)
If compilers did "what the committee intended", then when t
is not a pack:
A<void(type<decltype(t)>...)>::f<0>(1)
could mean
A<void(int...)>::f<0>(1)
which is
A<void(int, ...)>::f<0>(1)
and if f
is a template
(your code makes it an int
, but I think swapping the two should work) this would be fine. But the standard apparently currently disagrees?
So if http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1390 was implemented, then you could swap your two A
specializations. The void(T...,...)
specialization should have a template<int> void f(int)
, and the T
specialization should have a static const int
.
Now in the case where A<>
is dependent (on the size of a pack), ::f
is an int
and does not need template
. In the case where A<>
is not dependent, ::f
is a template
but does not need disambiguation.
We can replace the type<decltype(t)>...
with:
decltype(sizeof(decltype(t)*))...
and sizeof(decltype(t)*)
is of non-dependent type (it is std::size_t
), decltype
gives us a std::size_t
, and the ...
is treated as a old-school ...
arg. This means void(std::size_t...)
becomes a non-dependent type, so A<void(std::size_t...)>
is not dependent, so ::f
being a template is not a template in a dependent context.
In the case where t
is a parameter pack with one element
decltype(sizeof(decltype(t)*))...
becomes
std::size_t
but in a dependent context (one copy per element in t
pack). So we get
A<void(std::size_t)>::f
which is presumed to be a scalar value, so
A<void(std::size_t)>::f<0>(1)
becomes an expression evaluating to false
.
(Chain of logic generated in a discussion with Johannes in comments in original question).
Your second case is ill-formed; A<void(type<decltype(t)>...)>::f<0>(1)
should be
A<void(type<decltype(t)>...)>::template f<0>(1)
// ~~~~~~~~~
For the first case, both compilers are behaving incorrectly; this was considered sufficiently confusing that CWG 1520 was raised to query the correct behavior; the conclusion was that pack expansion should be applied before alias substitution:
The latter interpretation (a list of specializations) is the correct interpretation; a parameter pack can't be substituted into anything, including an alias template specialization. CWG felt that this is clear enough in the current wording.
This is reminiscent of CWG 1558 (alias templates and SFINAE), which was fixed for C++14, but per the above even C++11 compilers are expected to get this correct, so it is disappointing that gcc and clang get it wrong (though in fairness they do behave correctly in simpler cases, including the motivating example in CWG 1520). Note that MSVC had a similar bug till recently; it is fixed in VS2015.
Your code (only in the first case) is correct; but as a workaround, you could alter your alias template to use and discard its template parameter, fixing your program for both compilers - of course that means that your CWG 1390 exploit will cease to be valid:
template<typename T> using type = decltype(((int(*)(T*))(0))(0)); // int
However, I don't think your CWG 1390 trick can work as presented, since even though the expansion-substitution of type<decltype(t)>...
is not dependent on the types of t...
, it is dependent on their number:
template<typename T> struct A { template<int> static void f(int) {} };
template<> struct A<void(int, int, int)> { static const int f = 0; };
As Yakk points out, it can be made to work if you swap the member function template and data member, since a data member is OK in dependent context.
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