The following snippet of C++17 code compiles in GCC and CLang, but in Visual C++ it gives these errors:
<source>(14): error C2672: 'f': no matching overloaded function found
<source>(14): error C2784: 'std::ostream &f(std::ostream &,const container<int> &)': could not deduce template argument for 'const container<int> &' from 'const std::vector<int,std::allocator<int>>'
<source>(5): note: see declaration of 'f'
https://godbolt.org/z/aY769qsfK
#include <vector>
template< template <typename...> typename container >
void f (const container< int > &)
{ }
int main()
{
std::vector<int> seq = {1, 2, 3};
f<std::vector>(seq); // OK
f(seq); // ERROR
}
Note that this code is similar to one of the answers in Why compiler cannot deduce template template argument?
Is it a problem of the code? Or a problem in Visual C++? Maybe some ambiguity in the C++ standard that is interpreted different in GCC and Visual C++?
I have encountered this as well with Visual C++ and I think in this regard the Visual C++ compiler is not compliant with the C++17 standard and your code is correct (but your code won't work with an
std::vector
with custom allocator!). The standard containers have in fact two template parameters: The value type and the allocator (which defaults tostd::allocator<T>
). Prior to C++17 template template matching required the template parameters to match exactly while in C++17 this was relaxed to include default arguments as well. Yet for some reason Visual C++ seems still to expect the second template argumentstd::allocator<T>
and not assume the given default argument.The following sections will discuss the template template matching for the different standards in more detail. At the end of the post I will suggest alternatives that will make your code compile on all said compilers which takes the form of SFINAE with two two template arguments (so that it works with custom allocators as well) for C++17 and
std::span
for C++20 and onwards.std::span
actually does not need any template at all.Template parameters of
std::
ContainersAs pointed out in the post that you linked already standard-library containers such as
std::vector
,std::deque
andstd::list
actually have more than one template parameter. The second parameterAlloc
is a policy trait which describes the memory allocation and has a default valuestd::allocator<T>
.Contrary
std::array
actually uses two template parametersT
for the data type andstd::size_t N
for the container size. This means if one wants to write a function that covers all said containers one would have to turn to iterators. Only in C++20 there is a class template for contiguous sequences of objectsstd::span
(which is sort of a super-concept that encapsulates all of the above) that relaxes this.Template template matching and the C++ standard
When writing a function template whose template arguments themselves depend on template parameters you will have to write a so called template template function, meaning a function of the form:
Note that strictly according to the standard template template parameters would have to be of declared with
class
not withtypename
prior to C++17. You could certainly somehow circumvent such a template template construct (from C++11 onwards) with a very minimal solution such as (Godbolt)which assumes that the container contains a static member variable
value_type
which is then used to define the underlying data type of the elements. This will work for all saidstd::
containers (including thestd::array
!) but is not very clean.For template template function there exist particular rules which actually changed from C++14 to C++17: Prior to C++17 a template template argument had to be a template with parameters that exactly match the parameters of the template template parameter it substitutes. Default arguments such as the second template argument for the
std::
containers, the aforementionedstd::allocator<T>
, were not considered (See the "Template template argument" section here as well as in the section "Template template arguments" on the page 317 of this working draft of the ISO norm or the final C++17 ISO norm):Therefore prior to C++17 one would have to write a template mentioning the allocator as a default value manually as follows. This works also in Visual C++ but as all the following solutions will exclude the
std::array
(Godbolt MSVC):You could achieve the same thing in C++11 also with variadic templates (so that the data-type is the first and the allocator the second template argument of the template parameter pack
T
) as follows (Godbolt MSVC):Now in C++17 actually the following lines should compile and work with all
std::
containers with thestd::allocator<T>
(See section 5.7 on pages 83-88, in particular "Template Template Matching" on page 85, of "C++ Templates: The complete guide (second edition)" by Vandevoorde et al., Godbolt GCC).The quest for a generic
std::
container templateNow if your goal is to use a generic container that only holds integers as template arguments and you have to guarantee that it compiles on Visual C++ as well then you have following options:
You could extend the minimalistic unclean version with a
static_assert
to make sure that you are using the correct value type (Godbolt). This should work for all kinds of allocators as well as thestd::array
but it is not very clean.You could add the
std::allocator<T>
as a default template argument which has the disadvantage that your template then won't work if somebody uses a container with custom allocator and will neither work withstd::array
(Godbolt):Similar to your code you could specify the allocator as second template argument yourself. Again this won't work with another type of allocator (Godbolt):
So probably the cleanest approach prior to C++20 would be to use SFINAE to SFINAE out (meaning you add a certain structure inside the template which makes the compilation file if it does not meet your requirements) all other implementations that are not using the data type
int
withtype_traits
(std::is_same
from#include <type_traits>
, Godbolt)or which are not integer types (
std::is_integral
, Godbolt) as this is much more flexible regarding the template parameterAlloc
:Furthermore this can be extended easily with logical or
||
and logical and&&
. Since C++14 one might also the corresponding aliases and writestd::enable_if_t<std::is_same_v<T,int>>
instead ofstd::enable_if<std::is_same<T,int>::value>::type
which makes it a little less awkward to read.Finally in the newest standard C++20 you should even be able use the long-awaited concepts (
#include <concepts>
) using the Container concept (see also this Stackoverflow post) e.g. as follows (Wandbox)And similar in C++20 there exists
std::span<T>
which unlike all solutions above works withstd::array
as well (Wandbox)