I've distilled a problem that's confounded me to the minimal example shown below. Basically the gcc compiler doesn't accept NULL as a template parameter of type void*.
Is there a workaround for this?
Please note that I'm restricted to C++03.
#define NULL 0
template<typename T = void , T* = NULL>
struct Foo
{
};
typedef Foo<> FooType;
int main()
{
}
Edit: online version
There is no solution to your problem: C++03 pointer template parameters have to designate an object. Using NULL as a non-type template parameter is a feature that was added in N1905, which came out at least one year after C++03.
14.3.2 Template non-type arguments
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
(NEW) a constant expression that evaluates to a null pointer value (4.10); or
(NEW) a constant expression that evaluates to a null member pointer value (4.11); or
a pointer to member expressed as described in 5.3.1.
One option is to declare a "null struct" with a member that you can use instead of a real NULL:
template<typename T>
struct null
{
static T value;
};
template<typename T, T* = &null<T>::value>
struct Foo
{
};
Of course, accessing null<T>::value has well-defined semantics and it won't crash; the purpose is just to have an address that is guaranteed to be different from the address of any other object.
Note that in any case, it will be impossible for you to use T = void. You can't use the null workaround with it because void is an incomplete type; and you can't cast anything to a void pointer to use as a non-type template argument.
Related
Why I can't pass this pointer as a template argument:
template <const char* T>
struct MyStruct
{};
int main()
{
static constexpr const char* func_name = nullptr;
MyStruct<func_name>{}; // Works
static constexpr const char* func_name2 = __func__;
MyStruct<func_name2>{};
// A template argument may not reference a non-external entity
// MyStruct: invalid expression as a template argument for 'T'
//error: the address of ‘__func__’ is not a valid template argument
}
I want to pass the name of the function as a template argument, and no workaround seems possible. Why?
According to the GCC documentation:
The identifier __func__ is implicitly declared by the translator as if, immediately following the opening brace of each function definition, the declaration
static const char __func__[] = "function-name";
So it's as if it's declaring that inside the function, then I assign my constexpr const char* to it, and then pass it as the template argument. That should work, does it not?
I know that there are gotchas when it comes to const char* as a template argument. Before I was having trouble with passing a string literal (they're not allowed as template arguments). But in this case it seems I'm passing the address of a static array, which is a valid argument, no?
__func__ is explicitly disallowed.
[temp.arg.nontype]
3 For a non-type template-parameter of reference or pointer type, or for each non-static data member of reference or pointer type in a non-type template-parameter of class type or subobject thereof, the reference or pointer value shall not refer to or be the address of (respectively):
[...]
a predefined __func__ variable ([dcl.fct.def.general]), or
[...]
So GCC is correct.
You should also note that the GCC documentation states it is "as if" there is such a variable. There doesn't have to be one.
The content and exact whereabouts of __func__ is implementation defined, so just like string literals it is not constrained enough to play well with the one-definition rule when determining the linkage of templated entities.
Consider the following class S containing a function pointer, and a constexpr object s of that class initialized with a lambda:
struct S
{
void (*f) ();
};
constexpr S s { []{} };
Now if I write a template X with a non-type template parameter of type S, and instantiate it over s like this:
template<S> struct X {};
using x = X<s>;
clang compiles the code, but gcc complains with:
error: '<lambda()>::_FUN' is not a valid template argument of type 'void (*)()' because '<lambda()>::_FUN' is not a variable
using x = X<s>;
^
Here's the program.
The code seems fine to me, and I'm not sure what the error message is referring to. So is this code valid?
Note that both compilers accept the code if X instead has a non-type template parameter of reference type to S, like this:
template<S const &> struct X {};
This question was inspired by another similar question.
The code is valid.
[temp.arg.nontype]/2:
A template-argument for a non-type template-parameter shall be a converted constant expression ([expr.const]) of the type of the template-parameter.
[expr.const]/10:
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 [...]
(There's no implicit conversion in this case.)
[expr.const]/11:
A constant expression is [...] or a prvalue core constant expression whose value satisfies the following constraints:
if the value is an object of class type, each non-static data member of reference type refers to an entity that is a permitted result of a constant expression,
if the value is of pointer type, it contains [...] the address of a non-immediate function [...],
if the value is of pointer-to-member-function type, [...],
if the value is an object of class or array type, each subobject satisfies these constraints for the value.
An entity is a permitted result of a constant expression if [...] or if it is a non-immediate function.
(All functions are non-immediate except for the consteval ones. [dcl.constexpr])
Therefore, s is a valid converted constant expression of type S. In addition, it is not subject to [temp.arg.nontype]/3 (which only applies to pointer/reference to objects).
That is, s is a valid template argument.
I'm not sure what the error message is referring to
It's nonsense.
The error is emitted from invalid_tparm_referent_p inside GCC. It was extracted from code for handling pointer to object type when class non-type template paramter was implemented (4be5c72). Apparently, the implementer forgot to update this function to account for the pointer-to-function case.
I've reported the bug as https://gcc.gnu.org/PR97700.
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.
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.
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.