75

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?

TylerH
  • 20,799
  • 66
  • 75
  • 101
jbgs
  • 2,795
  • 2
  • 21
  • 28

6 Answers6

33

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.

30

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.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
13

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.

Levi Morrison
  • 19,116
  • 7
  • 65
  • 85
7

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

Laurent LA RIZZA
  • 2,905
  • 1
  • 23
  • 41
  • 1
    Unfortunately your code isn't valid. The proposal doesn't make that any syntax changes. `The reason is that the current grammar allows any declaration to be parameterized`. Try it on [coliru](http://coliru.stacked-crooked.com/) using `clang++ --std=c++1y`. –  Jan 16 '14 at 13:44
  • @remyabel: I don't get it. I used two features: template variables and template lambdas. Template lambdas are not accepted yet. – Laurent LA RIZZA Jan 16 '14 at 16:14
  • Oh, generic lambdas. It still won't compile, but I've tried to get close to it: http://coliru.stacked-crooked.com/a/54ab174f073762c2 –  Jan 17 '14 at 13:56
  • I corrected the lambda code in my post. I used `[key] = value` instead of `insert`. – Laurent LA RIZZA Jan 17 '14 at 14:44
  • Crud! It does not work. I can't capture `storage` in the lambda because it's not a variable. It's a variable template... – Laurent LA RIZZA Jan 17 '14 at 14:55
  • I have a compiling example at [the question I posted here](https://stackoverflow.com/questions/21188052/variable-templates-generic-lambdas-for-stdmap) (scroll to the bottom for the live example link), however it requires explicit instantiations for every pair you will use, and each map is a different variable. –  Jan 17 '14 at 15:02
  • Indeed. It kinda defeats the purpose. The only remaining solution for my idea to work is a global variable template with a free function template. – Laurent LA RIZZA Jan 17 '14 at 15:03
  • The prohibition against so-called ‘local templates’ in C++ is a longstanding one — even in the face of upcoming polymorphic lambdas. – Luc Danton Jan 20 '14 at 12:13
  • You can already do something similar to this using the `using` keyword: `template using store = map;` – RamblingMad Apr 09 '14 at 10:40
  • @CoffeeandCode: That only creates a new templated type name, it does not declare one variable per type argument. – Laurent LA RIZZA Apr 10 '14 at 14:10
  • There are means to get it to compile, but there are link errors (http://coliru.stacked-crooked.com/a/5db647f2bf85420f). The `storage` cannot be local, and generic lambdas now exist. – akim Nov 22 '16 at 20:44
5

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.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • Between this feature and the shorthands/helpers like `std::conditional_t`, etc. I often wonder why they came so late. The "[rules of thumb](https://accu.org/content/conf2013/Bjarnes_Talk.pdf)" make sense in principal, but items like "Don’t add features just to follow fashion" and “being able to do something is not sufficient reason for doing it” sure sound like explanations for the flak C++ TMP syntax takes. Maybe if I knew more about the TR/TS process I'd understand. – John P Oct 28 '17 at 17:17
0

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>);
    ...
}
Tiger Hwang
  • 300
  • 1
  • 5
  • 16
  • Side note: `CT` is apparently short for character type but it's just ambiguous. You could use `charT` or `char_type` instead. – digito_evo Feb 17 '22 at 20:21