Void pointer as template argument in C++ - c++

The following does not compile:
template<void *p>
class X {
// ...
};
int r;
int main()
{
X<&r> x;
return 0;
}
The error message is
x.cc:10:6: error: could not convert template argument ‘& r’ to ‘void*’
Explicitly casting &r to (void *) doesn't help either. The error message becomes:
x.cc:10:14: error: could not convert template argument ‘(void*)(& r)’ to ‘void*’
Which part of the standard specifies that behaviour?
The GCC version is gcc version 5.2.1 20151003 (Ubuntu 5.2.1-21ubuntu2)
Edit:
Please note that using e.g. int * instead of void * works as expected.
Edit: (answering myself)
It does not work with gcc HEAD 6.0.0 20151016 (experimental) when specifying -std=c++1z, neither with implicit nor with explicit casting to "void *".
It does work with clang HEAD 3.8.0 (trunk 250513) and has been since (at least) clang 3.6.0 (tags/RELEASE_360/final) when specifying --std=c++1z and explicitly casting to *void *".
Without the explicit cast, clang complains as follows:
x.cc:10:7: error: conversion from 'int *' to 'void *' is not allowed in a converted constant expression
Responsible for fixing this bug in the c++ language specification is N4268 which clang already implements.

Normally, there is a conversion allowed for any pointer to void*.
[C++11, 4.10/2] A prvalue of type “pointer to cv T,” where T is an
object type, can be converted to a prvalue of type “pointer to cv
void”. The result of converting a “pointer to cv T” to a “pointer to
cv void” points to the start of the storage location where the object
of type T resides, as if the object is a most derived object (1.8) of
type T (that is, not a base class subobject). The null pointer value
is converted to the null pointer value of the destination type.
However, for non-type template arguments, certain conversions are specified:
[C++11, 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 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. [...]
By omission, we can reason this conversion is simply not allowed.

I can't quote you the chapter and verse off the top of my head (edits are welcome), but what you are trying to do is not allowed in c++.
Template parameters must be known at compile time. Pointers are only resolved at link time unless:
they are defaulted using = nullptr in the template argument list.
they are member function pointers (which are known at compile time since they are merely offsets).
for example, this will compile:
template<void * = nullptr>
class X {
// ...
};
int r;
int main()
{
X<nullptr> x;
return 0;
}

Related

Expected behavior on out-of-range template parameters?

template<bool b = 2> void foo(void) {}
template void foo();
template<unsigned char n = 258> void bar(void) {}
template void bar();
GCC instantiates foo< true> and bar<2>; Clang rejects both with "error: non-type template argument evaluates to 2, which cannot be narrowed to type 'bool' [-Wc++11-narrowing]".
Is the above code valid? Is this a bug in one of them?
Versions used: Clang 3.8.0-2ubuntu4, GCC 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.2)
This is gcc bug 57891 and 60715.
From [temp.arg.nontype]:
A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter.
From [expr.const]:
A converted constant expression of type T is an expression, implicitly converted to type T, where the converted
expression is a constant expression and the implicit conversion sequence contains only [...] integral conversions (4.7) other than narrowing conversions (8.5.4),
From [dcl.init.list]:
A narrowing conversion is an implicit conversion [...] from an integer type or unscoped enumeration type to an integer type that cannot represent all the
values of the original type, except where the source is a constant expression whose value after integral
promotions will fit into the target type.
Narrowing conversions (e.g. 2 to bool or 258 to char) are ill-formed for template non-type parameters.

Derived to base conversion for template argument

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.

The address of a function matching a bool vs const void* overload

I'm reading Unexpected value using random number generator as a function in C++ and the comments and current answer say that the user is outputting the address of the function. That sounded reasonable. I assumed that a function-to-pointer conversion was occurring and therefore matching the const void* overload, however upon testing it myself, I get different results in GCC/Clang vs MSVC. The following test program:
#include <iostream>
void test()
{
}
void func(bool)
{
std::cout << "bool";
}
void func(const void*)
{
std::cout << "const void*";
}
int main()
{
func(test);
}
outputs bool in GCC/Clang (coliru)
and const void* in MSVC (rextester warning live collaboration link)
N3337 says:
[conv.func]
An lvalue of function type T can be converted to a prvalue of type
"pointer to T." The result is a pointer to the function.
[conv.bool]
A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to
member type can be converted to a prvalue of type bool. A zero
value, null pointer value, or null member pointer value is converted
to false; any other value is converted to true. A prvalue of type
std::nullptr_t can be converted to a prvalue of type bool; the
resulting value is false.
So a pointer which is not a null pointer value converted to bool should equal true, explaining the warning given by GCC/Clang.
Then Table 12 Conversions under [over.ics.scs] gives a function-to-pointer conversion an "Exact Match" rank and boolean conversions "Conversion" rank. [over.ics.rank]/4 then says:
Standard conversion sequences are ordered by their ranks: an Exact
Match is a better conversion than a Promotion, which is a better
conversion than a Conversion. Two conversion sequences with the same
rank are indistinguishable unless one of the following rules applies:
— A conversion that does not convert a pointer, a pointer to member,
or std::nullptr_t to bool is better than one that does.
— [...]
I am not a language lawyer so I hope that I quoted the right sections.
However MSVC will call the const void* overload even if the bool overload is absent, and vice versa: GCC/Clang will call the bool overload even if the const void* overload is absent. So I'm not clear on the conversions here. Can somebody clear this up for me?
Seems like a bug (or extension) in MSVC. The standard does not define any standard conversions from a "pointer to function" to a "pointer to void."
It's hard to provide a quote for the absence of something, but the closest I can do is C++11 4.10/2 [conv.ptr]:
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer
to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of
the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type
T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the
destination type.
Together with 3.9/8 [basic.types]:
An object type is a (possibly cv-qualified) type that is not a function type, not a reference type, and not a
void type.
(emphasis mine)
Using /Za to disable extensions will disable the non-standard conversion.

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> >.

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.