Reputation: 1493
I'd like to ask whether the following code sample should compile:
#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;
template <template <class...> class C>
struct convert_container
{
using type = C<double>;
// Visual Studio requires this to be:
// using type = C<double, std::allocator<doble>>
};
int main()
{
std::cout << typeid(convert_container<std::vector>::type).name();
}
The code compiles fine with GCC 4.8.1 and Clang 3.4 but not with Visual Studio 2013. The error I get:
error C2976: 'std::vector' : too few template arguments
c:\program files (x86)\microsoft visual studio 12.0\vc\include\vector(650) : see declaration of 'std::vector'
c:\users\michał\documents\visual studio 2013\projects\transform\transform.cpp(14) : see reference to class template instantiation 'convert_container<std::vector>' being compiled
What does the standard say about this? Am I required to explicitly state all the parameters (including defaulted ones) when using the template template parameter C
or is this just a bug in VC++?
Context: The issue araised from Constructor's answer to my previous question: https://stackoverflow.com/a/23874768/2617356
When searching the archives I've found this question: Default values in templates with template arguments ( C++ ) It's basically about the same problem, the question author states that default parameters for template template parameter "had to be" explicitly stated. However, the asker accepted solution that's not quite applicable in my case. The question was not about what is the standard-conforming behaviour, so I believe this is not a duplicate.
Upvotes: 21
Views: 2132
Reputation:
Consider the similar
template <typename = void, int = 0> struct A { };
template <template <typename ...> class T> struct B : T<> { };
template class B<A>;
This is clearly covered by the standard (14.3.3p3 if you're interested, I won't quote it, as GCC and clang do both implement the rule already), where the use of A
as a template argument for B
is disallowed because of the non-type template parameter. That rule makes no sense if the instantiation of a template template parameter could make use of the template template argument's default template arguments, so the behaviour of MSVC and Intel is more consistent than that of GCC and clang.
Of course, the reasoning "if this were valid, the standard would have inconsistencies" doesn't actually mean it isn't valid, only that it shouldn't be valid. To actually check what the standard says:
14.1 Template parameters [temp.param]
10 The set of default template-arguments available for use with a template declaration or definition is obtained by merging the default arguments from the definition (if in scope) and all declarations in scope in the same way default function arguments are (8.3.6).
8.3.6 Default arguments [dcl.fct.default]
4 Declarations in different scopes have completely distinct sets of default arguments. That is, declarations in inner scopes do not acquire default arguments from declarations in outer scopes, and vice versa.
Although not specifically intended to address this use of default template arguments, I think it does manage to do so. Nikos Athanasiou has already included the part of the standard that says any default template arguments of C
do get used:
14.1 Template parameters [temp.param]
14 A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter.
Since C
's default template arguments are used, std::vector
's aren't, and MSVC and Intel seem to be correct here.
And to come up with an example that clearly shows that GCC and clang cannot be considered to conform here:
template <typename = char, typename = short>
struct A { };
template <template <typename = void, typename ...> class T>
struct B {
using type = T<>;
};
Both GCC and clang treat B<A>::type
as A<void, short>
, taking one default template argument from T
, and another from A
, even though the standard disallows merging of default arguments (and hence default template arguments) in declarations in different scopes.
A workaround for you, to avoid the need to type out the allocator argument, could be to use a template alias:
template <template <class...> class C>
struct convert_container
{
using type = C<double>;
};
template <typename T>
using vector_default_alloc = std::vector<T>;
int main()
{
std::cout << typeid(convert_container<vector_default_alloc>::type).name();
}
I cannot test on MSVC right now, but Intel accepts it, and I see no reason why this variant would be invalid.
Upvotes: 3
Reputation: 31499
A (seemingly related) quote from the standard 14.1 Template parameters
14 . A template-parameter of a template template-parameter is permitted to have a default template-argument. When such default arguments are specified, they apply to the template template-parameter in the scope of the template template-parameter.
[ Example:
template <class T = float> struct B {};
template <template <class TT = float> class T> struct A {
inline void f();
inline void g();
};
template <template <class TT> class T> void A<T>::f() { // (*)
T<> t; // error - TT has no default template argument
}
template <template <class TT = char> class T> void A<T>::g() {
T<> t; // OK - T<char>
}
— end example ]
This is the only verse posing limitations to the use of default template parameters of template template parameters (verse 9,11,12 pose limitations on the definition/specification)
As stressed in the comments, OP's case does not involve a default parameter in convert_container
(so the above does not apply explicitly). IMHO there are two ways of interpreting the situation :
using type = C<double>
is a type alias for a class template; that class "loses" the right to use default template parameters, since it's passed as a template template parameter and all default arguments (of that TT parameter) lie outside the scope of the "typedefing". Then VS is correct.
By tracking the instantiation process : Say a correct compiler instantiates the struct as so (it's just a type substitution - no actual representation of the actual instantiation process is implied)
struct convert_container
{
using type = vector<double>;
};
then OP's case seems fairly legit (and gcc/clang are correct)
This compiles in VS2013
template <template <class...> class C>
using tt = C<double>;
int main()
{
std::cout << typeid(tt<std::vector>).name();
}
So the arguments of default template parameters being non legal to pass to template template parameters seems more and more shaky.
Upvotes: 1