alias template substitution and deduction failure with gcc - c++

I believe that I have found a problem with gcc's alias template handling. Essentially, gcc appears to fail to correctly substitute the alias's template-id for an alias template instantiation when referring to the types by reference.
I was able to whittle a messy real-world problem down to a minor variation on the non-normative example provided in the C++ 11 standard section temp.alias (14.5.7/2):
#include <vector>
using namespace std;
template <class T>
using Vec = vector<T, allocator<T>>;
template <template <class> class TT>
void f1(TT<int> v);
template <template <class> class TT>
void f2(TT<int>& v);
template <template <class, class> class TT>
void g1(TT<int, allocator<int>> v);
template <template <class, class> class TT>
void g2(TT<int, allocator<int>>& v);
void foo()
{
Vec<int> v;
f1(v); // gcc and clang both correctly yield no matching function error
g1(v);
f2(v); // clang yields a no matching function error
g2(v); // gcc yields a no matching function error
}
As noted above, clang 3.3 (recent pull from svn) and gcc (4.7.2, 4.8.0, and 4.8.1) agree on the handling of f1/g1 in conformance with the standard, but differ on the handling of f2/g2 (to be clear, all tested versions of gcc accept the call to f2() and error on the call to g2()). The difference between f1/g1 and f2/g2 is of course that the latter pair uses a reference parameter.
All indications, both in this example and in my real problem, are that gcc is not correctly converting the type of the instantiation of the alias template (e.g. Vec<int>) to the aliased type (e.g. vector<int, allocator<int>>) prior to trying to deduce the template parameter for the instantiations of f2 and g2.
My question is: first, is indeed gcc incorrect and clang correct here, and second, is there any straightforward way (other than not using the alias template) to convince gcc to reject f2 and match g2.

Indeed this is a GCC bug. One workaround is to simply add a typedef.
typedef Vec<int> vit;
vit v;
Given that this is effective, an identity metafunction would probably also work.

It looks like the compiler has issue with the inference. Explicitly invoking the template enables a work around -- I can't speak to why gcc fails to infer as well as icpc or clang.
For icpc (ICC) 13.1.0 20130121:
f1<Vec> (v); // gcc and clang both correctly yield no matching function error
g1(v);
f2<Vec>(v); // clang yields a no matching function error
g2(v); // gcc yields a no matching function error
For gcc (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8):
f1<Vec> (v); // gcc and clang both correctly yield no matching function error
g1(v);
f2<Vec>(v); // clang yields a no matching function error
g2<vector>(v); // gcc yields a no matching function error

Related

Variadic template works in gcc but not in clang

I'm learning C++ using the books listed here. In particular I read about variadic templates. Now, to further clear my concepts I'm also writing simple examples and trying to understand them by myself using debugger and cout statements.
One such program that compiles with gcc but is rejected by clang is given below. Demo.
template<typename T, typename... V>
struct C
{
T v(V()...);;
};
int main()
{
C<int> c; //works with gcc but rejected in clang
C<int, double, int, int> c2; //same here: works with gcc but rejected in clang
}
So my question is which compiler is right here(if any)?
Here is the error that clang gives:
<source>:6:12: error: '...' must be innermost component of anonymous pack declaration
T v(V()...);;
^~~
...
1 error generated.
Compiler returned: 1
This is a GCC bug. The correct syntax is T v(V...());, which Clang accepts and GCC rejects (incorrectly).
In a function parameter list, the ... that expands a pack must precede the parameter name, or be in the place where the name would otherwise be. This is more commonly seen in cases like V &&... v.
The related grammar is: function-definition -> declarator -> parameters-and-qualifiers -> ... -> parameter-declaration -> abstract-declarator ("abstract" = no parameter name).
[dcl.fct]/26 says that if there's an ambiguity in a function parameter list whether ... is a pack expansion or a C-style variadic parameter, it resolves to a pack expansion. But in this case there's no ambiguity, V()... leaves the pack unexpanded (which should be a compilation error) and ... should be a C-style variadic parameter.
GCC is wrong in accepting the program because a function parameter pack is introduced using an ellipsis (...) prior
to (or in the place of) the function parameter name.
This means in your example the correct way to declare the function with parameter of type pointer to function is T v(V...());. But note that gcc rejects this modified code. Demo
Here is the gcc bug report:
GCC accepts invalid program involving function declaration with pack expansion
Note
It quite interesting to note that gcc rejects T v(V...()) but accepts T v(V...b()). Demo
template<typename T, typename... V>
struct C
{
//-------vvv---------->this is the correct way which gcc rejects
T v(V...());
//-------vvv---------->but gcc accepts this when we name the parameter
T v(V...b());
};

Program using class non type template parameter accepted by gcc but rejected by clang

I am learning C++ using the resources listed here. In particular, I have learnt that in C++20 we can have a class type as a non type template parameter. Now, to better understand the concept, I tried the following example that is accepted by msvc and gcc but rejected by clang. My question is which compiler is right?
Demo
struct Impl
{
constexpr Impl(std::initializer_list<int>)
{
}
};
struct Bar{};
template<typename T, Impl impl>
struct Foo
{
};
int main()
{
constexpr Foo<Bar, {1,2,3,4}> foo; //works in msvc & gcc but rejected in clang
return 0;
}
GCC and MSVC are wrong in accepting the program as it is ill-formed for the reason explained below.
The standard doesn't allow braced init list {1,2,3,4} to be a template argument. This can be seen from temp.names#1:
template-argument:
constant-expression
type-id
id-expression
And since {1,2,3,4} is not any of the above three listed constructs, it cannot be used as a template argument.
Additionally note that {1,2,3,4} is not an expression and does not have a type.
This is the reason clang generates the error saying:
vvvvvvvvvvvvvvvvvvv
error: expected expression
constexpr Foo<Bar, {1,2,3,4}> foo;
The gcc bug has been reported as:
GCC accepts invalid program involving {1,2,3,4} as template argument
And msvc bug as:
MSVC accepts invalid program involving {1,2,3,4} as template argument

Can alias templates have default template parameters?

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

GCC error when typedef name coincides with variadic template parameter name

I stumbled on a strange interaction between typedef and variadic template parameters that I'd like to understand. The following code compiles with clang but gives an error with GCC:
template<typename T> // no error if this is not a template
struct Traits;
#pragma GCC diagnostic ignored "-Wunused-parameter"
template<typename ...args>
void function(args... e) {}
template<typename T>
struct Caller {
typedef typename Traits<T>::types traits_types; // no error if this is changed to a 'using' directive
template<typename ...types> // no error if the pack is converted to a single parameter
static void method(types... e) {
function<traits_types>(e...);
}
};
GCC behavior
When I compile (not link) this with GCC, I get an error on line 14:
$ g++-9.2.0 -Wall -Wpedantic -Wextra -std=c++11 -c test.cpp
test.cpp: In static member function ‘static void Caller<T>::method(types ...)’:
test.cpp:14:31: error: parameter packs not expanded with ‘...’:
14 | function<traits_types>(e...);
| ~~~~~~~~~~~~~~~~~~~~~~^~~~~~
test.cpp:14:31: note: ‘types’
$
It acts as if GCC first substitutes in the definition of traits_types, which would produce
template<typename ...types>
static void method(types... e) {
function<typename Traits<T>::types>(e...);
}
and then evaluates template parameter substitution, at which point it sees the last occurrence of the token types as an unexpanded parameter pack and produces an error accordingly.
I've tested this with GCC 6.4.0, 7.3.0, 8.2.0, 8.3.0, 9.1.0, and 9.2.0, as well as a couple older versions, and the behavior is consistent among all of them.
clang behavior
However, when I compile this with clang, it works fine.
$ clang++-8 -Wall -Wpedantic -Wextra -std=c++11 -c test.cpp
$
This seems as if it first substitutes in for the parameter pack types and then handles the name traits_types.
I see this behavior consistently in clang 6.0.0, 7.0.1, and 8.0.1, and a couple older versions.
Question
In standard C++11, is GCC correct to give the error that it does, or is the code valid? Or is it undefined/implementation-defined/otherwise unspecified?
I've looked through much of cppreference.com's content on templates and typedefs without finding anything that clearly addresses this case. I also checked several other questions (1, 2, 3, 4, 5, etc.), all of which seem similar but don't quite apply to this situation, as far as I can tell.
If this is in fact a compiler bug, a link to a relevant issue in the bug tracker confirming that GCC (or clang, if applicable) doesn't handle this correctly would settle the question pretty well.
Yes it's a bug. What you observed as "it acts as if GCC first substitutes in the definition of traits_types", is followed by a manifestation of GCC bug 90189:
Source:
struct A {
using CommonName = char;
};
template <typename T, typename... CommonName>
struct B {
using V = typename T::CommonName;
};
template struct B<A>;
Output:
<source>:7:37: error: parameter packs not expanded with '...':
7 | using V = typename T::CommonName;
| ^
<source>:7:37: note: 'CommonName'
Compiler returned: 1
Rejected by all GCC versions. Accepted by clang, msvc.
GCC acts like you wrote typename Traits<T>::types directly, and then it gets confused by the dependent name types being the same as the name of the template parameter pack. You can get around it by giving the pack a different name, but in standard C++ the dependent name can be the same as the name of the pack. Because one must be qualified, whereas the other is unqualified, there should be no ambiguity.

Variadic template aliases as template arguments

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.