template<typename T>
class A { };
U x;
A<U> ... // OK
A<x> ... // ERROR
If we can, then how do we make a specialization of class A
whose argument is an object of any type?
template<typename T>
class A { };
U x;
A<U> ... // OK
A<x> ... // ERROR
If we can, then how do we make a specialization of class A
whose argument is an object of any type?
Before considering "argument is an object of any type", we should first understand the limitations for "argument is an object". (See also How to use an object instance as template argument?)
A clarification before getting into this: What's the difference between an argument and a parameter? A template parameter is the placeholder used in a definition, such as this question's <typename T>
. An argument is the replacement provided when the definition is used, such as this question's <U>
.
Technically, a template parameter that is neither a type nor a template cannot be an object of class type, but it can be a (lvalue) reference to an object. C++20 relaxes this a bit but not all the way to "any type", so I'll ignore this caveat for now.
In practice, converting a parameter from being an object to being a reference is just a bit of syntax juggling. Still, there is an important bit of semantics to this: different objects produce different template instantiations, even if you believe the objects to be "equal". If you ignore this point, you might needlessly bloat your code. Are you sure this is consistent with your goal?
A template argument that is neither a type nor a template must be a compile-time constant. This is a refrain that is commonly heard when discussing templates. However, it has a perhaps surprising implication when dealing with references. In order for an object reference to be a compile-time constant expression, it must refer to an object with static storage duration. That is, the object must be:
static
, orextern
.This should make sense if you think about it. A reference needs the address of the object to which it refers. Only an object that is allocated when the program begins has an address that is known at compile time. The address of an object local to a function depends on where in the call stack that particular function call lies, which is a run-time quality.
While a template parameter could be a reference to an object of any type, it is not true that any object of any type could be used as the corresponding argument.
Subject to the above restrictions, allowing an object of any type is just a matter of allowing an object and allowing a type. There are two things that can vary. One of these can hide behind the "auto" keyword.
template<const auto & Object>
class A {};
If you want to be more restrictive about which types are accepted (such as objects of class type, as opposed to objects of fundamental type), there are various type properties that can be used with SFINAE.
Example:
template<const auto & Object>
class A {};
// A class for demonstration purposes
class U {};
// A global variable (declared at namespace scope)
U x;
int main()
{
// A static variable
static U y;
// A local variable
U z;
// Try to instantiate the template
A<x> compiles;
A<y> fine;
//A<z> fails;
// Suppress unused variable warnings
(void) compiles;
(void) fine;
(void) z;
}
Now that you know you could do this, stop to think if you should. You probably should not. Maybe it's just a bit of over-engineering? Trying to be "cool"? How bad could it get?