Derived to base conversion for template argument - c++

struct CL1{};
struct CL2:CL1{};
template<CL1*>
struct TMPL{};
CL2 cl2;
int main()
{
TMPL<&cl2> tmpl; //error: could not convert template argument ‘& cl2’ to ‘CL1*’
return 0;
}
The Standard 2003 14.3.2/5 says:
for a non-type template-parameter of type pointer to object,
qualification conversions (4.4) and the array-to-pointer conversion
(4.2) are applied. [Note: In particular, neither the null pointer
conversion (4.10) nor the derived-to-base conversion (4.10) are
applied. 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. ]
Why such restrictions applied?

2 reasons IMHO:
Addresses are not known until link time. That's well after any template expansion decisions have been made. Indeed in position independent code, addresses are not known until run time.
There is a longstanding ambiguity between (type *)0 and int(0). c++11 cures this with the nullptr value of nullptr_t class.

Related

Why void* as template parameter works as function parameter but not template parameter?

I have two version of my_begin:
template<typename T, typename std::enable_if<std::is_array<T>::value>::type* = 0>
typename std::decay<T>::type my_begin(T& array) {
return array;
}
and
template<typename T>
typename std::decay<T>::type my_begin(T& array,
typename std::enable_if<std::is_array<T>::value>::type* = 0) {
return array;
}
However the first one does not work and gives error:
int a[10];
int* a_it = my_begin(a);
error:
main.cpp:17:30: note: template argument deduction/substitution failed:
main.cpp:16:80: error: could not convert template argument '0' to 'std::enable_if<true, void>::type* {aka void*}'
template<typename T, typename std::enable_if<std::is_array<T>::value>::type* = 0>
But the second one works. When I change 0 in the first one to nullptr, it works too (but still not working for NULL). I do understand that in template it requires explicit casting (in this case, from int to void*, but why the second one does not require it?
Another question, if I remove the whitespace between * and =, it also failed. Why is that?
§14.1 [temp.param]/p4 says:
A non-type template-parameter shall have one of the following
(optionally cv-qualified) types:
integral or enumeration type,
pointer to object or pointer to function,
lvalue reference to object or lvalue reference to function,
pointer to member,
std::nullptr_t.
Read literally, this disallows void* template parameters altogether. void* is an object pointer type but isn't a pointer to object type (§3.9.2 [basic.compound]/p3):
The type of a pointer to void or a pointer to an object type is
called an object pointer type. [ Note: A pointer to void does
not have a pointer-to-object type, however, because void is not an
object type. —end note ]
If we assume it's a defect and that the standard really meant to say "object pointer type", then using 0 and company is still disallowed by §14.3.2 [temp.arg.nontype]/p5 (emphasis added):
The following conversions are performed on each expression used as a
non-type template-argument. If a non-type template-argument cannot be
converted to the type of the corresponding template-parameter then the
program is ill-formed.
[...]
for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the array-to-pointer conversion
(4.2) are applied; if the template-argument is of type std::nullptr_t,
the null pointer conversion (4.10) is applied. [ Note: In particular,
neither the null pointer conversion for a zero-valued integer literal
(4.10) nor the derived-to-base conversion (4.10) are applied. 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. However, both (int*)0 and nullptr
are valid template-arguments for a non-type template-parameter of type
“pointer to int.” —end note ]
= 0 works for function default arguments because those are subject to the normal conversion rules, which allows an integer literal with value 0 to convert to a null pointer, rather than the special rules for template arguments.
if I remove the whitespace between * and =, it also failed. Why is that?
Maximum munch. If the whitespace is removed, *= is a single token (the compound assignment operator). Just like in C++03 when you had to put a space between the >s in std::vector<std::vector<int> >.

what is wrong with this pointer to member conversion?

What is wrong with this code? I thought I could convert due to this answer:
Is it safe to "upcast" a method pointer and use it with base class pointer?
struct B
{
void f(){}
};
struct D : B
{
virtual ~D(){}
};
template <typename FP, FP fp>
void g()
{
}
int main()
{
g<void (D::*)(), &B::f>();
return 0;
}
Error:
t.cpp:18:27: error: could not convert template argument '&B::f' to 'void (D::*)()'
g<void (D::*)(), &B::f>();
This doesn't work either:
g<void (D::*)(), static_cast<void (D::*)()>(&B::f)>();
This is disallowed by the standard (C++11, [temp.arg.nontype]§5):
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
...
For a non-type template-parameter of type pointer to member function, if the template-argument is of type std::nullptr_t, the null member pointer conversion (4.11) is applied; otherwise, no conversions apply. If the template-argument represents a set of overloaded member functions, the matching member function is selected from the set (13.4).
(Emphasis mine)
Casts are not allowed either, because of [temp.arg.nontype]§1:
A template-argument for a non-type, non-template template-parameter shall be one of:
...
a pointer to member expressed as described in 5.3.1.
Where 5.3.1§4 reads:
A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses.
This combines to say that a cast experssion is not allowed as a non-type template argument.
So, while such conversions are possible at runtime, it seems there's no way to use them as template arguments.

Derived class type in template argument doesn't compile

This is the code snippet I have been hopelessly stuck on.
template <class T, T nt>
class C;
struct base{
int i;
} b;
struct derived : base{} d;
C<base*,&d> obj;
Why this giving error could not convert template argument &d to base*?
When matching an argument to a parameter that is a pointer/reference, derived to base conversions are not considered even if the conversions are valid in other circumstances.
14.3/5 [Standard quote just for reference]
If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
....
for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the
array-to-pointer conversion (4.2) are applied. [Note: In particular, neither the null pointer conversion
(4.10) nor the derived-to-base conversion (4.10) are applied. 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. ]

Why can't I downcast pointer to members in template arguments?

If I make a pointer-to-base-member, I can convert it to a pointer-to-derived-member usually, but not when used within a template like Buzz below, where the first template argument influences the second one. Am I fighting compiler bugs or does the standard really mandate this not work?
struct Foo
{
int x;
};
struct Bar : public Foo
{
};
template<class T, int T::* z>
struct Buzz
{
};
static int Bar::* const workaround = &Foo::x;
int main()
{
// This works. Downcasting of pointer to members in general is fine.
int Bar::* y = &Foo::x;
// But this doesn't, at least in G++ 4.2 or Sun C++ 5.9. Why not?
// Error: could not convert template argument '&Foo::x' to 'int Bar::*'
Buzz<Bar, &Foo::x> test;
// Sun C++ 5.9 accepts this but G++ doesn't because '&' can't appear in
// a constant expression
Buzz<Bar, static_cast<int Bar::*>(&Foo::x)> test;
// Sun C++ 5.9 accepts this as well, but G++ complains "workaround cannot
// appear in a constant expression"
Buzz<Bar, workaround> test;
return 0;
}
It simply isn't allowed. According to §14.3.2/5:
The following conversions are performed on each expression used as a non-type template-argument. If a non-type template-argument cannot be converted to the type of the corresponding template-parameter then the program is ill-formed.
— for a non-type template-parameter of integral or enumeration type, integral promotions (4.5) and integral conversions (4.7) are applied.
— for a non-type template-parameter of type pointer to object, qualification conversions (4.4) and the array-to-pointer conversion (4.2) are applied.
— For a non-type template-parameter of type reference to object, no conversions apply. The type referred to by the reference may be more cv-qualified than the (otherwise identical) type of the template argument. The template-parameter is bound directly to the template-argument, which must be an lvalue.
— For a non-type template-parameter of type pointer to function, only the function-to-pointer conversion (4.3) is applied. If the template-argument represents a set of overloaded functions (or a pointer to such), the matching function is selected from the set (13.4).
— For a non-type template-parameter of type reference to function, no conversions apply. If the template-argument represents a set of overloaded functions, the matching function is selected from the set (13.4).
— For a non-type template-parameter of type pointer to member function, no conversions apply. If the template-argument represents a set of overloaded member functions, the matching member function is selected from the set (13.4).
— For a non-type template-parameter of type pointer to data member, qualification conversions (4.4) are applied.
I've emphasized the conversion regarding pointer to data members. Note that your conversion (§4.11/2) is not listed. In C++0x, it remains the same in this regard.

0 not a valid FILE* when provided as a template argument

The following code
#include <stdio.h>
template <typename T, T v> class Tem
{
T t;
Tem()
{
t = v;
}
};
typedef Tem<FILE*,NULL> TemFile;
when compiled in a .mm file (Objective C++) by Xcode on MacOS X, throws the following error:
error: could not convert template argument '0' to 'FILE*'.
What's going on, please? The code in question compiled fine under MSVC. Since when is the 0 constant not a valid pointer to anything? Is this an artifact of Objective C++ (as opposed to vanilla C++)?
According to the standard, you are out of luck. There is no way to initialize a pointer argument to anything besides the address-of a global. §14.3.2/1:
A template-argument for a non-type,
non-template template-parameter shall
be one of:
an integral constant-expression of integral or enumeration type; or
the name of a non-type template-parameter; or
the address of an object or function with external linkage, including
function templates and function
template-ids but excluding non-static
class members, expressed as &
id-expression where the & is optional
if the name refers to a function or
array, or if the corresponding
template-parameter is a reference; or
a pointer to member expressed as described in 5.3.1 .
§14.3.2/5:
for a non-type template-parameter of
type pointer to object, qualification
conversions (4.4) and the
array-to-pointer conversion (4.2) are
applied. [Note: In particular, neither
the null pointer conversion (4.10) nor
the derived-to-base conversion (4.10)
are applied. 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. ]
However, Comeau accepts this invalid workaround:
typedef Tem<FILE*, (FILE *) NULL > TemFile;
And this code has a slim chance of compliance: I can't find where the standard specifically says that a default expression is used verbatim in place of a a missing argument, and I can't find a matching known defect. Anyone have a reference?
#include <stdio.h>
template <typename T, T *v = (T*) 0> class Tem
{
T t;
Tem()
{
t = v;
}
};
typedef Tem<FILE> TemFile;
For more portability, you might consider creating a bogus FILE FILE_NULL;, pass &FILE_NULL, and test for pointer-equality with that instead of zero.
Did you try something like this?
typedef Tem<FILE*,((FILE*)NULL)> TemFile;
Perhaps it's trying to figure out the type of NULL.