I posted this message several days ago, improperly on gcc@xxxxxxxxxxx .
But I think my question is not entirely solved, so I post it here again.
My original message:
Please check the following small program:
#include <stdio.h>
template<typename USER_TYPE, typename RET_TYPE, RET_TYPE (*pfn)(USER_TYPE), USER_TYPE valInvalid>
class CEnsureCleanup_array
{
int m_ArraySize;
USER_TYPE *m_ar; // This member representing the object array ptr
public:
// Default constructor clears all pointer elements to invalid-value
CEnsureCleanup_array(int ArraySize) : m_ArraySize(ArraySize) {
m_ar = new USER_TYPE[m_ArraySize];
for(int i=0; i<m_ArraySize; i++) m_ar[i] = valInvalid;
}
// The destructor performs the cleanup.
~CEnsureCleanup_array() { Cleanup(); }
// Helper methods to tell if the value represents a valid object or not..
int IsValid(int idx) { return(m_ar[idx] != NULL); }
int IsInvalid(int idx) { return(!IsValid(idx)); }
USER_TYPE& operator[](int idx) {
return m_ar[idx];
}
operator USER_TYPE*() {
return m_ar; // Return the address of the first array element.
}
void Cleanup()
{
for(int i=0; i<m_ArraySize; i++)
if (IsValid(i)) {
pfn(m_ar[i]); // Cleanup the object.
}
delete m_ar;
}
};
struct MyClass
{
int m_i;
MyClass(int i) : m_i(i) {}
~MyClass() {
printf("MyClass dtor(%d)\n", m_i);
}
};
#define MakeCleanupClass_array(CecClassName, RET_TYPE_of_CleanupFunction, pCleanupFunction, USER_TYPE, valInvalid) \
typedef CEnsureCleanup_array<USER_TYPE, RET_TYPE_of_CleanupFunction, pCleanupFunction, valInvalid> CecClassName;
void DeleteMyClass(MyClass *p){ delete p; }
MakeCleanupClass_array(CecMyClass, void, DeleteMyClass, MyClass*, 0)
int main()
{
CecMyClass armc(2);
// By using CecMyClass, armc[0] & armc[1] can be automatically deleted
// on `armc' destruction.
armc[0] = new MyClass(0);
armc[1] = new MyClass(1);
return 0;
}
It compiles OK on Microsoft Visual Studio.NET 2005 or even MSVC
6.0 SP6,and the output is:
MyClass dtor(0)
MyClass dtor(1)
However, with gcc 4.1.0(SUSE Linux), compilation error:
t1.cpp:56: error: could not convert template argument '0' to 'MyClass*'
t1.cpp:56: error: invalid type in declaration before ';' token
t1.cpp: In function 'int main()':
t1.cpp:63: error: invalid types 'CecMyClass[int]' for array subscript
t1.cpp:64: error: invalid types 'CecMyClass[int]' for array subscript
I'm really baffled! Since 0 can be assigned to any pointer
variable, then why "0 could not be converted to MyClass* "?
Furthermore, if a try to change the line just above the main()
definition to:
MakeCleanupClass_array(CecMyClass, void, DeleteMyClass, MyClass*, (MyClass*)0)
Compiler error changes to:
t1.cpp:56: error: a cast to a type other than an integral or enumeration type cannot appear in a constant-expression
t1.cpp:56: error: template argument 4 is invalid
t1.cpp:56: error: invalid type in declaration before ';' token
t1.cpp: In function 'int main()':
t1.cpp:63: error: invalid types 'CecMyClass[int]' for array subscript
t1.cpp:64: error: invalid types 'CecMyClass[int]' for array subscript
I really cannot figure out why gcc spout those errors while that
case of template usage has very clear meaning and is absolutely
unambiguous.
Can you dear gcc developers give me an explanation?
And later, Ian Lance Taylor replied:
The C++ standard is clear: "Although 0 is a valid template-argument
for a non-type template-parameter of integral type, it is not a valid
template-argument for a non-type template-parameter of pointer type."
(14.3.2 Template non-type arguments [temp.arg.nontype]).
Oh yes, according to Ian, I found that very statement on page 243 of
ISO/IEC 14882. But why that rule? If you try that code with MSVC, you'll
know that that code works just what I(and everyone, I think) expect, no
language and semantic ambiguity!
Then, only after I change the problematic line to (two lines this time):
MyClass g_McNull = 0;
MakeCleanupClass_array(CecMyClass, void, DeleteMyClass, MyClass*, &g_McNull)
can gcc compiles in peace. Again, I want to ask: Since an global object
address(a constant after link) can be accepted as the template argument,
*then why NULL is forbidden?!*
A standard is not a bible, a standard is created by human and work for
human. It may contain flaws, vagueness, or even errors. What I want to
ask gcc gurus here regarding this problem is: If we allow NULL to be a
valid "template-argument for a non-type /template-parameter/ of pointer
type", does it cause harm to C++ code already existed in the world?
Since C++ 98 allows an int number for a "non-type /template-parameter/
of *integral type*", then why in the world does he not allow 0 for a
"non-type /template-parameter/ of pointer type"? It seems the standard
does not state this. If you know, I'm very grateful to hear from you.