C++14 Variable Templates: what is their purpose? Any usage example?
C++TemplatesC++14C++ Problem Overview
C++14 will allow the creation of variables that are templated. The usual example is a variable 'pi' that can be read to get the value of the mathematical constant π for various types (3 for int
; the closest value possible with float
, etc.)
Besides that we can have this feature just by wrapping a variable within a templated struct or class, how does this mix with type conversions? I see some overlapping.
And other than the pi
example, how would it work with non-const
variables? Are there any usage examples to understand how to make the most of such a feature and what its purpose is?
C++ Solutions
Solution 1 - C++
> And other than the pi example, how would it work with non-const > variables?
Currently, it seems to instantiate the variables separately for the type. i.e., you could assign 10 to n<int>
and it would be different from the template definition.
template<typename T>
T n = T(5);
int main()
{
n<int> = 10;
std::cout << n<int> << " "; // 10
std::cout << n<double> << " "; // 5
}
If the declaration is const
, it is readonly. If it's a constexpr
, like all constexpr
declarations, it has not much use outside constexpr
(ressions).
> Besides that we can have this feature just by wrapping a variable > within a templated struct or class, how does this mix with type > conversions?
It's meant to be a simple proposal. I am unable to see how it affects type conversions in a significant way. As I already stated, the type of the variable is the type you instantiated the template with. i.e., decltype(n<int>)
is int. decltype((double)n<int>)
is double and so on.
> Any usage example to understand how to make the most of such a feature > and what its purpose is?
N3651 provides a succinct rationale.
> Alas, existing C++ rules do not allow a template declaration to > declare a variable. There are well known workarounds for this > problem: > > > • use constexpr static data members of class templates > > > • use constexpr function templates returning the desired values > > These workarounds have been known for decades and well documented. > Standard classes such as std::numeric_limits are archetypical > examples. Although these workarounds aren’t perfect, their drawbacks > were tolerable to some degree because in the C++03 era only simple, > builtin types constants enjoyed unfettered direct and efficient > compile time support. All of that changed with the adoption of > constexpr variables in C++11, which extended the direct and efficient > support to constants of user-defined types. Now, programmers are > making constants (of class types) more and more apparent in programs. > So grow the confusion and frustrations associated with the > workarounds.
...
> The main problems with "static data member" are: > > > • they require "duplicate" declarations: once inside the class > > template, once outside the class template to provide the "real" > > definition in case the con- stants is odr-used. > > > • programmers are both miffed and confused by the necessity of providing twice the same > > declaration. By contrast, "ordinary" constant declarations do not need > > duplicate declarations.
...
> Well known examples in this category are probably static member
> functions of numeric_limits, or functions such as
> boost::constants::pi<T>()
, etc. Constexpr functions templates do not
> suffer the "duplicate declarations" issue that static data members
> have; furthermore, they provide functional abstraction. However, they
> force the programmer to chose in advance, at the definition site, how
> the constants are to be delivered: either by a const reference, or by
> plain non- reference type. If delivered by const reference then the
> constants must be systematically be allocated in static storage; if
> by non-reference type, then the constants need copying. Copying isn’t
> an issue for builtin types, but it is a showstopper for user-defined
> types with value semantics that aren’t just wrappers around tiny
> builtin types (e.g. matrix, or integer, or bigfloat, etc.) By
> contrast, "ordinary" const(expr) variables do not suffer from this
> problem. A simple definition is provided, and the decision of
> whether the constants actually needs to be layout out in storage only
> depends on the usage, not the definition.
Solution 2 - C++
> we can have this feature just by wrapping a variable within a templated struct or class
Yes, but that would be gratuitous syntactic salt. Not healthy for the blood pressure.
pi<double>
conveys the intent better than pi<double>::value
. Short and to the point. That's enough of a reason in my book to allow and encourage this syntax.
Solution 3 - C++
Another practical example for C++14's variable templates is when you need a function for passing something into std::accumulate
:
template<typename T>
T const & (*maxer) (T const &, T const &) = std::max<T>;
std::accumulate(some.begin(), some.end(), initial, maxer<float>);
Note that using std::max<T>
is insufficient because it can't deduce the exact signature. In this particular example you can use max_element
instead, but the point is that there is a whole class of functions that share this behavior.
Solution 4 - C++
I wonder whether something along these lines would be possible: (assuming availability of template lambdas)
void some_func() {
template<typename T>
std::map<int, T> storage;
auto store = []<typename T>(int key, const T& value) { storage<T>[key] = value; };
store(0, 2);
store(1, "Hello"s);
store(2, 0.7);
// All three values are stored in a different map, according to their type.
}
Now, is this useful?
As a simpler use, notice that the initialization of pi<T>
uses explicit conversion (explicit call of a unary constructor) and not uniform initialization. Which means that, given a type radians
with a constructor radians(double)
, you can write pi<radians>
.
Solution 5 - C++
Well, you can use this to write compile time code like this:
#include <iostream>
template <int N> const int ctSquare = N*N;
int main() {
std::cout << ctSquare<7> << std::endl;
}
This is a significant improvement over the equivalent
#include <iostream>
template <int N> struct ctSquare {
static const int value = N*N;
};
int main() {
std::cout << ctSquare<7>::value << std::endl;
}
that people used to write to perform template metaprogramming before variable templates were introduced. For non-type values, we were able to do this since C++11 with constexpr
, so template variables have only the advantage of allowing computations based on types to the variable templates.
TL;DR: They don't allow us to do anything we couldn't do before, but they make template metaprogramming less of a PITA.
Solution 6 - C++
I have a use case here.
template<typename CT> constexpr CT MARK = '%';
template<> constexpr wchar_t MARK<wchar_t> = L'%';
which are used in a string processing template.`
template <typename CT>
void ProcessString(const std::basic_string<CT>& str)
{
auto&& markpos = str.find(MARK<CT>);
...
}