Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why class template below compile in Visual Studio 2019 and does not with gcc 8.3?

Why code below compile in Visual Studio 2019 and does not with gcc 8.3?

#include<array>
template<typename T> class myClass {
public:
template<unsigned int N> myClass(const std::array<T, N>& elems) { /* do something */ }
};

int main() {
    std::array<int, 10> A;
    myClass<int> Tabular(A);
}

This is a snippet I extract from my student's project because it looks weird to me. I try this with gcc 8.3 and as I suspect the compiler complains that template argument deduction is failed. So I told my student that this does not work. But he argued that this do compile in VS without warning, I checked and he was right.

Because I'm very far from calling myself an expert, I cannot explain to myself and my student whether/why/what is wrong.

like image 506
Fall We Avatar asked Mar 16 '20 09:03

Fall We


2 Answers

Both Gcc and MSVC are correct. The type of the 2nd template parameter of std::array is defined as std::size_t, and how std::size_t is defined depends on implementation.

typedef /*implementation-defined*/ size_t;

std::size_t is the unsigned integer type of the result of the sizeof operator as well as the sizeof... operator and the alignof operator (since C++11).

The bit width of std::size_t is not less than 16. (since C++11)

Then when std::size_t is defined as the same as unsigned int the code compiles fine, otherwise it would fail; type mismatch causes the non-type template argument deduction fails.

Change unsigned int to std::size_t then the code is guaranteed to be compiled well with any decent compilers. e.g.

template<typename T> class myClass {
public:
template<std::size_t N> myClass(const std::array<T, N>& elems) { /* do something */ }
};

And about why type mismatch causes the non-type template argument deduction fails.

(emphasis mine)

If a non-type template parameter is used in the parameter list, and the corresponding template argument is deduced, the type of the deduced template argument ( as specified in its enclosing template parameter list, meaning references are preserved) must match the type of the non-type template parameter exactly, except that cv-qualifiers are dropped, and except where the template argument is deduced from an array bound—in that case any integral type is allowed, even bool though it would always become true:

template<int i> class A { };
template<short s> void f(A<s>); // the type of the non-type template param is short

void k1()
{
    A<1> a;  // the type of the non-type template param of a is int
    f(a);    // P = A<(short)s>, A = A<(int)1>
             // error: deduced non-type template argument does not have the same
             // type as its corresponding template argument
    f<1>(a); // OK: the template argument is not deduced, 
             // this calls f<(short)1>(A<(short)1>)
}

template<int&> struct X;
template<int& R> void k2(X<R>&);
int n;
void g(X<n> &x) {
    k2(x); // P = X<R>, A = X<n>
           // parameter type is int&
           // argument type is int& in struct X's template declaration
           // OK (with CWG 2091): deduces R to refer to n
}
like image 168
songyuanyao Avatar answered Sep 18 '22 02:09

songyuanyao


Whenever you have template errors like this, the best thing you can do is try different compilers, and see what errors they will give you. CompilerExplorer is a really good site for this

GCC says:

<source>:4:26: note: candidate: 'template<unsigned int N> myClass<T>::myClass(const std::array<T, N>&) [with unsigned int N = N; T = int]'

    4 | template<unsigned int N> myClass(const std::array<T, N>& elems) { /* do something */ }

      |                          ^~~~~~~

<source>:4:26: note:   template argument deduction/substitution failed:

<source>:9:27: note:   mismatched types 'unsigned int' and 'long unsigned int'

    9 |     myClass<int> Tabular(A);

      |  

Clang says:

<source>:4:26: note: candidate template ignored: substitution failure: deduced non-type template argument does not have the same type as the corresponding template parameter ('unsigned long' vs 'unsigned int')

template<unsigned int N> myClass(const std::array<T, N>& elems) { /* do something */ }

As you can see, both GCC and Clang failed to subsitute long unsigned int (std::size_t) with a unsigned int.

like image 33
Kaldrr Avatar answered Sep 22 '22 02:09

Kaldrr