The C++ program (somewhat unexpectedly, at first, to me) compiles and runs fine, except for the line commented at the end of main()
, which is a compile-time error if it is uncommented.
#include <typeinfo>
#include <iostream>
struct Foo {
int x;
};
template <typename T>
void create(char *buffer)
{
std::cout << "creating " << typeid(T).name() << std::endl;
new (buffer) T();
}
template <typename T>
void destroy(char *buffer)
{
std::cout << "destroying " << typeid(T).name() << std::endl;
((T*)buffer)->~T();
}
int main(int argc, char **argv)
{
char buffer[sizeof(Foo) > sizeof(bool) ? sizeof(Foo) : sizeof(bool)];
// create/destroy Foo via template function calls
create<Foo>(buffer);
destroy<Foo>(buffer);
// now do the above explicitly...
new (buffer) Foo();
((Foo*)buffer)->~Foo();
// create/destroy bool via template function calls
create<bool>(buffer);
destroy<bool>(buffer);
// now do the above explicitly...
new (buffer) bool();
// ((bool*)buffer)->~bool(); // oops, doesn't work
return 0;
}
I gather from this that C++ (or at least g++'s idea of C++) allows an "explicit destructor call" of a template parameter type, even when manually doing the type replacement oneself results in a syntax error (since bool
doesn't really actually have a destructor to call).
To be more explicit, the line:
((T*)buffer)->~T();
compiles and runs fine when T is instatiated on a bool
, but doing the same thing with an actual bool
:
((bool*)buffer)->~bool();
is a syntax error.
I'm discovered this behavior as I was doing template metaprogramming and find it a very helpful, so I'm guessing it's standard and was added specifically to handle cases like the one I have above. Does anyone know for sure if this is actually the case, and when this behavior was standardized if it is?
In addition, can anyone point to what the exact wording in the standard is that allows this, and the scope of the situations it allows? (I'm not adept at reading standardese so I have a hard time figuring this out myself.)
This is indeed valid C++ (and has been since C++98, as far as I know), and known as a pseudo-destructor call. N4431 §5.2.4 [expr.pseudo]:
1 The use of a pseudo-destructor-name after a dot
.
or arrow->
operator represents the destructor for the non-class type denoted by type-name or decltype-specifier. The result shall only be used as the operand for the function call operator()
, and the result of such a call has typevoid
. The only effect is the evaluation of the postfix-expression before the dot or arrow.2 The left-hand side of the dot operator shall be of scalar type. The left-hand side of the arrow operator shall be of pointer to scalar type. This scalar type is the object type. The cv-unqualified versions of the object type and of the type designated by the pseudo-destructor-name shall be the same type. Furthermore, the two type-names in a pseudo-destructor-name of the form
nested-name-specifier_opt type-name :: ~ type-name
shall designate the same scalar type.
A pseudo-destructor-name is one of (§5.2 [expr.post]):
nested-name-specifier_opt type-name :: ~ type-name
nested-name-specifier template simple-template-id :: ~ type-name
~ type-name
~ decltype-specifier
While a type-name is one of (§7.1.6.2 [dcl.type.simple])
class-name
enum-name
typedef-name
simple-template-id
bool
is not a type-name, so the ~bool()
version is a syntax error. Inside a template, a template type parameter is a typedef-name (§14.1 [temp.param]/p3), which is a type-name, so the ~T()
version compiles.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With