The following code is very short, but causes different compilers to behave disagree:
#include <tuple>
template <typename T = int, typename... Ts>
using tpl = std::tuple<T, Ts...>;
tpl x; // I would assume this should be std::tuple<int>
Clang and MSVC say that template argument deduction doesn't work for alias templates, ICC says that template arguments are missing, while GCC has an internal compiler error. I would normaly consider that an indication that alias templates do not undergo deduction - especially that according to cppreference they don't (which I am aware is not an official resource and just a reference) - but I would like to be sure.
Is this code really ill-formed or are alias template default template arguments simply not yet implemented in these compilers? I thought that they were added in C++17 or C++20.
C++20 introduces CTAD for alias templates
An alias template may indeed have default template arguments, and it is legal to have default template arguments to template parameters that are followed by a template parameter pack, as per [temp.param]/14:
If a template-parameter of a class template, variable template, or alias template has a default template-argument, each subsequent template-parameter shall either have a default template-argument supplied or be a template parameter pack. [...]
The default template argument is a red herring, however, and the key here is whether class template argument deduction is valid or not for alias templates, and we may minimize your example to the following one:
#include <tuple>
template <typename T>
using tpl = std::tuple<T>;
tpl x{1}; // should deduce tpl<int> in C++20
// Clang: error
// GCC 9.3: error
// GCC 10.1: ICE / internal compiler error
As per P1814R0(1), which was accepted for C++20, the minimal example above is indeed legal, but Clang is yet to implement P1814R0, explaining why Clang rejects it. GCC, on the other hand, lists P1814R0 as implemented for GCC 10, meaning it should accept it for C++20.
(1) As per C++20 and P1814R0 (Wording for Class Template Argument Deduction for Alias Templates), (/wording for original proposal P1021R4) CTAD is applicable also for alias templates, whilst however not allowing explicit deduction guides for them.
In C++17 you need to include the template argument list (even if it's empty) when using alias templates - there is no equivalent to class template argument deduction for alias templates in C++17:
#include <tuple>
template <typename T = int, typename... Ts>
using tpl = std::tuple<T, Ts...>;
tpl<> x; // OK in GCC and Clang
An ICE (internal compiler error) is always a bug, no matter if the code is ill-formed or well-formed, and as noted above GCC emits an ICE only for 10.1 and later, whereas it yields an error for previous releases.
Thus, GCC apparently have a ICE regression for 10.1 (which was suspiciously listed as the target when CTAD for alias templates were implemented). It is at the very least related to the following bug report:
Bug 96199 - [10/11 Regression] internal compiler error: in tsubst_copy with CTAD for alias templates
Which however is listed as resolved, whereas your example still yields an ICE for a GCC trunk that includes the fix to 96199.
We may finally note that GCC successfully applies CTAD for the alias template where we only use a template parameter pack:
#include <tuple>
template <typename... Ts>
using tpl = std::tuple<Ts...>;
tpl x{1}; // OK
but that if we replace std::tuple by std::vector in the minimal example:
#include <vector>
template <typename T>
using vec = std::vector<T>;
vec x{{1}}; // GCC 10.1: ICE
we get another kind of ICE for GCC 10.1 (and forward), whereas adding a default template argument and replacing the braced-direct-initialization with default-initialization is accepted.
#include <vector>
template <typename T = int>
using vec = std::vector<T>;
vec x; // GCC 10.1: OK
Related
I am learning C++ templates using the resource listed here. In particular, read about template argument deduction. Now, after reading, to further clear up my concept of the topic I tried the following example that compiles in gcc but not in clang and msvc. Demo
template<typename T = int> void f()
{
}
template<typename T> void func(T)
{
}
int main()
{
func(f); //works in gcc but not in clang and msvc
func(f<>); //works in all
}
As we can see, the above example compiles fine in gcc but not in clang and msvc. My question is which compiler is right here according to the latest standard?
This is CWG 2608 and the program is well-formed so that gcc is correct in accepting the program.
From temp.arg.explicit#4 which was added due to cwg 2608, the default template argument can be used and the empty template argument list <> can be omitted.
If all of the template arguments can be deduced or obtained from default template-arguments, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.
(emphasis mine)
Note the bold highlighted part in the above quoted statement, which means that the empty template argument list <> can be omitted in your example because all of the template arguments can be obtained from default template-arguments.
Further, over.over#3 can also be used here to see that the specialization generated from temp.arg.explicit is added to the overloaded set:
The specialization, if any, generated by template argument deduction ([temp.over], [temp.deduct.funcaddr], [temp.arg.explicit]) for each function template named is added to the set of selected functions considered.
(emphasis mine)
This means that at the end, the call func(f); is well-formed and uses the generated specialization f<int>.
I've noticed that MSVC sometimes fails to deduce non-type parameters that other compilers accept, and recently came upon a simple example involving the function noexcept specifier (which is part of the function's signature since C++17):
template <typename T> struct is_nocv_method : public std::false_type { };
template <typename ReturnT, typename ClassT, bool IsNoexcept, typename... Args>
struct is_nocv_method<ReturnT (ClassT::*)(Args...) noexcept(IsNoexcept)> : std::true_type { };
Godbolt suggests gcc 12.1 and clang 14.0 accept this without issue, but MSVC 14.31 (cl.exe 19.31) fails to compile, claiming IsNoexcept cannot be deduced. Is this a compiler defect?
Demo
A non-type template parameter cannot be deduced from a noexcept-specifier.
[temp.deduct.type]/8 gives the list of contexts from which template parameters can be deduced. Essentially, it can be read as a list of ways to "unwrap" the argument type, and a list of positions in the unwrapped type from which template arguments can be deduced.
For example, the item T (T::*)(T) implies that if the parameter type and the argument type are both pointers to member functions, then template parameters can be deduced from the return type, the class type, and any argument types (for the member function), should they appear there.
You'll notice that there is no item of the form T() noexcept(i), T(T) noexcept(i), and so on.
Yet, some compilers choose to allow this kind of deduction anyway, probably because it is convenient. I would support adding it to the standard.
Edit (Oct 20, 2022): It appears that this will be changing in C++23: i will be deducible from noexcept(i). According to the issues list, this has "DR" status, which means it's retroactive (presumably to C++17).
See this bug report: https://developercommunity.visualstudio.com/t/noexcept-is-not-deducible-in-partial-spe/841571
And this issue, approved by EWG: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1018r9.html#CWG2355
It appears that deduction from noexcept specifiers is currently nonstandard, but that's considered an oversight and is set to be fixed.
All standard references below refers to N4659: March 2017 post-Kona working draft/C++17 DIS.
Consider the following snippet:
#include <type_traits>
template <int N> struct num {};
template <typename> struct A;
// (1)
template <int N> struct A<num<N>> { using type = bool; };
// (2)
template <long N> struct A<num<N>> { using type = char; };
static_assert(!std::is_same_v<long, int>, "");
// (A)
static_assert(std::is_same_v<A<num<1>>::type, bool>, "");
int main() {}
The static_assert at (A) is successful for GCC, but fails for Clang:
error: static_assert failed due to
requirement 'std::is_same_v<char, bool>' ""
Essentially, GCC picks the perfectly matching specialization (1), whereas Clang picks the specialization (2).
Similarly, if we remove the assertions as well as specialization (1):
template <int N> struct num {};
template <typename> struct A;
// (2)
template <long N> struct A<num<N>> { using type = char; };
int main() {
A<num<1>> a{};
(void)a;
}
Then GCC fails to compile the program whereas Clang accepts it.
GCC:
error: variable '`A<num<1> > a`' has initializer but incomplete type
This behaviour holds over various GCC and Clang versions, as well as various C++ language levels over these version (C++11, C++14, C++17, C++2a).
Question
Is the first snippet above in fact ill-formed (no diagnostic required?), or is either GCC or Clang wrong?
My guess is that this is ill-formed, but haven't been able to apply a relevant part of [temp.class.spec] to reject it. Perhaps [temp.class.spec]/8.1?
[temp.class.spec]/8.1 The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization. [ Example: [...] — end example ]
As far as I can tell, the first snippet is ill-formed (and a diagnostic is required); compilers should reject the program because of the partial specialization (2).
[temp.deduct.type]/18 applies here:
If P has a form that contains <i>, and if the type of i differs
from the type of the corresponding template parameter of the template
named by the enclosing simple-template-id, deduction fails. [...]
The associated example in the Standard uses a function template, but is otherwise very similar.
So the template argument of the partial specialization (2) can never be deduced, and [temp.class.spec.match]/3 applies:
If the template arguments of a partial specialization cannot be
deduced because of the structure of its template-parameter-list and
the template-id, the program is ill-formed.
Interestingly, I couldn't find a compiler that diagnoses this issue, not even EDG in strict mode. We could speculate that most compiler writers consider the benefits of having a diagnostic here not to be worth the effort of implementing the checks. This could mean that we might see the requirement in the paragraph above change in the future from ill-formed to ill-formed, no diagnostic required. However, this is pure speculation. In any case, I don't see it ever changing to well-formed; I can't think of a valid use for a partial specialization that never matches.
The wording of [temp.deduct.type]/18 was clarified by the resolution of CWG2091.
The standard is not nearly precise enough about the template argument deduction for a partial specialization ([temp.class.spec.match]/2) to definitively determine the meaning of your example. In particular, all deduction is ultimately defined in terms of types ([temp.deduct.type]), but there are no types involved for a non-type template argument.
The deduction for partial ordering among partial specializations handles this case in terms of an invented class template ([temp.class.order]/1.2), which brings to bear the rule that deduction fails for any mismatch between the type of a non-type template argument and its parameter ([temp.deduct.type]/18). That makes any use of A<num<…>> in your example ambiguous if both partial specializations match (avoiding the need to determine whether a narrowing conversion was involved in using the “unique value” synthesized for partial ordering ([temp.func.order]/3) as a template argument). However, if we apply the same rule to the matching itself, we find (as does GCC) that (2) never matches. In turn, that arguably should provoke a diagnostic for the specialization itself ([temp.class.spec.match]/3, as bogdan’s answer mentioned), although it’s not entirely obvious what “structure” there is meant to include if the error is to be diagnosable and no compiler rejects it.
Meanwhile, [temp.class.spec]/8.1 is certainly irrelevant: there are no specialized non-type arguments involved.
My GCC 4.9.1 does not give an error about this:
#include <iostream>
template<typename _Tp, typename... _Args>
struct IsCble { };
template<typename _Tp>
struct IsCble<_Tp> { static constexpr int value {4}; };
int main()
{
std::cout << IsCble<int>::value << std::endl;
return 0;
}
The same with GCC 5.1. But they should according to:
[temp.class.spec] 14.5.5\8.4
— The specialization shall be more specialized than the primary
template.
I think, in the code above the partial specialization is not more specialized than the primary template, because of:
[temp.deduct.type] 14.8.2.5\9.1
— if P does not contain a template argument corresponding to
Ai then Ai is ignored;
So it seems, they ignore 14.5.5\8.4 and resolve the ambiguity by:
[temp.class.spec.match] 14.5.5.1\1
This is done by matching the template arguments of the class template
specialization with the template argument lists of the partial
specializations.
Is it a conforming implementation (1.4\8)?
An omitted parameter is supposed to be more specialised than an empty parameter pack. The same question in a different context is the subject of an open issue:
CWG agreed that the example should be accepted, handling this case as a late tiebreaker, preferring an omitted parameter over a parameter pack.
So you're right based on the current text of the standard, but there's little point in compilers being adjusted to follow the current rules when those rules are expected to change.
First some code, then some context, then the question:
template <typename T> using id = T;
template <template <typename...> class F, typename... T>
using apply1 = F <T...>;
template <template <typename...> class F>
struct apply2
{
template <typename... T>
using map = F <T...>;
};
// ...
cout << apply1 <id, int>() << endl;
cout << apply2 <id>::map <int>() << endl;
Both clang 3.3 and gcc 4.8.1 compile this without error, applying the identity metafunction to int, so both expressions evaluate to a default int (zero).
The fact that id is a template <typename> while apply1, apply2 expect a template <typename...> did concern me in the first place. However, it is quite convenient that this example works because otherwise metafunctions like apply1, apply2 would have to be so much more involved.
On the other hand, such template aliases cause serious problems in real-world code that I cannot reproduce here: frequent internal compiler errors for gcc, and less frequent unexpected behavior for clang (only in more advanced SFINAE tests).
After months of trial and error, I now install and try the code on the (experimental) gcc 4.9.0, and here comes the error:
test.cpp: In instantiation of ‘struct apply2<id>’:
test.cpp:17:22: error: pack expansion argument for non-pack parameter ‘T’ of alias template ‘template<class T> using id = T’
using map = F <T...>;
^
Ok, so it seems this code was not valid all this time, but gcc crashed in various ways instead of reporting the error. Interestingly, while apply1, apply2 appear to be equivalent, the error is only reported for apply2 (which is much more useful in practice). As for clang, I really cannot say.
In practice, it seems I have no other way than to go along with gcc 4.9.0 and correct the code, even though it will become much more complex.
In theory, I would like to know what the standard says: is this code valid? If not, is the use of apply1 invalid as well? or only apply2?
EDIT
Just to clarify that all problems I've had so far refer to template aliases, not template structs. For instance, consider the following modification:
template <typename T> struct id1 { using type = T; };
// ...
cout << typename apply1 <id1, int>::type() << endl;
cout << typename apply2 <id1>::map <int>::type() << endl;
This compiles fine and prints 0 in both cases, on clang 3.3, gcc 4.8.1, gcc 4.9.0.
In most cases, my workarounds have been introducing an intermediate template struct before the alias. However, I am now trying to use metafunctions to parametrize generic SFINAE tests and in this case I have to use aliases directly, because structs should not be instantiated. Just to get an idea, a piece of the actual code is here.
ISO C++11 14.3.3/1:
A template-argument for a template template-parameter shall be the name of a class template or an alias template, expressed as id-expression.
Plus I don't see any special exceptions for variadic template template parameters.
On the other hand, such template aliases cause serious problems in real-world code that I cannot reproduce here: frequent internal compiler errors for gcc, and less frequent unexpected behavior for clang (only in more advanced SFINAE tests).
Root of problems can be in other places. You should try to localize code which causes internal compiler error - just remove unrelated parts one by one (or use some kind of binary search, i.e. divide-and-conquer) - and check if error is still here on each stage.
As for GCC 4.9.0 error, try to change
template <typename... T>
using map = F <T...>;
to
template <typename... U>
using map = F <U...>;
Maybe this would help to understand what GCC sees.