Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

C++17 in Detail: Templates

DZone's Guide to

C++17 in Detail: Templates

Welcome back! We continue our series by taking an up-close and personal look at templates and meta-programming in C++17. Read on for more!

· Web Dev Zone ·
Free Resource

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Introduction

Do you work a lot with templates and meta-programming?  With C++17 we get a few nice improvements: some are quite small, but also there are notable features as well! All in all, the additions should significantly improve writing template code.

Today I wrote about:

  • Template argument deduction for class templates.
  • template<auto>
  • Fold expressions.
  • constexpr if
  • Plus some smaller, detailed improvements/fixes.

BTW: if you’re really brave you can still use concepts! They are merged into GCC so you can play with them even before they are finally published.

Documents and Links

First of all, if you want to dig into the standards on your own, you can read the latest draft here:

N4659, 2017-03-21, Working Draft, Standard for Programming Language C++ - the link also appears on the isocpp.org.

WG21 P0636r0: Changes between C++14 and C++17

Compiler support: C++ compiler support

Moreover, I’ve prepared a list of concise descriptions of all of the C++17 language features:

Download a free copy of my C++17 Cheat Sheet!

It’s a one-page reference card, PDF.

There’s also a talk from Bryce Lelbach: C++Now 2017: C++17 Features

And have a look at my master C++17 features post: C++17 Features

Template Argument Deduction for Class Templates

I have good and bad news for you.

Do you often use make<T> functions to construct a templated object (like std::make_pair)?
With C++17 you can forget about (most of) them and just use a regular constructor. 
That also means that a lot of your code - those make<T> functions - can now be removed.

The reason?

C++17 filled a gap in the deduction rules for templates. Now the template deduction can happen for standard class templates and not just for functions.

For instance, the following code is (and was) legal:

void f(std::pair<int, char>);// call:f(std::make_pair(42, 'z'));

Because std::make_pair is a template function (so we can perform template deduction).

But the following wasn’t (before C++17)

void f(std::pair<int, char>);

// call:
f(std::pair(42, 'z'));

Looks the same, right? This was not OK because std::pair is a template class, and template classes could not apply type deduction in their initialization.

But now we can do that so that the above code will compile the under C++17 conformant compiler.

What about creating local variables like tuples or pairs?

std::pair<int, double> p(10, 0.0);
// same as
std::pair p(10, 0.0); // deduced automatically!

Try it in Compiler Explorer: example, GCC 7.1.

This can substantially reduce complex constructions like:

std::lock_guard<std::shared_timed_mutex, 
        std::shared_lock<std::shared_timed_mutex>> lck(mut_, r1);

The above can now become:

std::lock_guard lck(mut_, r1);

Note, that partial deduction cannot happen. You have to specify all the template parameters or none:

std::tuple t(1, 2, 3);              // OK: deduction
std::tuple<int,int,int> t(1, 2, 3); // OK: all arguments are provided
std::tuple<int> t(1, 2, 3);         // Error: partial deduction

Also, if you’re adventurous, you can create your custom class template deduction guides. See here for more information: recent post by Arne Mertz, Modern C++ Features - Class Template Argument Deduction.

By the way: why can't all make functions be removed? For example, consider make_unique or make_shared. Are they only for ‘syntactic sugar’? Or do they have other important uses? I’ll leave this as an exercise.

More details in:

MSVC not yet, GCC: 7.0, Clang: not yet.

Declaring Non-Type Template Parameters With Auto

This is another part of the strategy to use auto everywhere. With C++11 and C++14, you can use it to automatically deduce variables or even return types, plus there are also generic lambdas. Now you can also use it for deducing non-type template parameters.

For example:

template <auto value> void f() { }

f<10>();               // deduces int

This is useful, as you don’t have to have a separate parameter for the type of non-type parameter. Like:

template <typename Type, Type value> constexpr Type TConstant = value;
                // ^^^^                        ^^^^  
constexpr auto const MySuperConst = TConstant<int, 100>;

With C++17, it’s a bit simpler:

template <auto value> constexpr auto TConstant = value;
                             // ^^^^
constexpr auto const MySuperConst = TConstant <100>;

So no need to write Type explicitly.

As one of the advanced uses, a lot of papers/blogs/talks point to an example of Heterogeneous compile time lists:

template <auto ... vs> struct HeterogenousValueList {};
using MyList = HeterogenousValueList<'a', 100, 'b'>;

Before C++17, it was not possible to declare such lists directly, some wrapper class would have to be provided first.

More details in:

MSVC not yet, GCC: 7.0, Clang: 4.0.

Fold Expressions

With C++11, we got variadic templates which are a great feature, especially if you want to work with a variable number of input parameters to a function. For example, previously (pre-C++11) you had to write several different versions of a function (like one for one parameter, another for two parameters, another for three params…).

Still, variadic templates required some additional code when you wanted to implement ‘recursive’ functions like sum, all. You had to specify rules for the recursion:

For example:

auto SumCpp11(){
    return 0;
}

template<typename T1, typename... T>
auto SumCpp11(T1 s, T... ts){
    return s + SumCpp11(ts...);
}

And with C++17 we can write much simpler code:

template<typename ...Args> auto sum(Args ...args) 
{ 
    return (args + ... + 0); 
}

// or even:

template<typename ...Args> auto sum2(Args ...args) 
{ 
    return (args + ...);
}

The following are fold expressions over a parameter pack.

Expression Expansion
(… op pack) ((pack1 op pack2) op …) op packN
(init op … op pack) (((init op pack1) op pack2) op …) op packN
(pack op …) pack1 op (… op (packN-1 op packN))
(pack op … op init) pack1 op (… op (packN-1 op (packN op init)))

Also, by default, we get the following values for empty parameter packs (P0036R0):

Operator default value
&& true
|| false
, void()

Here’s a quite nice implementation of a printf using folds:

template<typename ...Args>
void FoldPrint(Args&&... args) {
    (cout << ... << forward<Args>(args)) << '\n';
}

Or a fold over a comma operator:

template<typename T, typename... Args>
void push_back_vec(std::vector<T>& v, Args&&... args)
{
    (v.push_back(args), ...);
}

In general, fold expressions allow you to write cleaner, shorter and probably easier to read code.

More details in:

MSVC not yet, GCC: 6.0, Clang: 3.6 (N4295)/3.9(P0036R0).

Constexpr If

This is a big one!

The static-if for C++!

The feature allows you to discard branches of an if statement at compile-time based on a constant expression condition.

if constexpr(cond)
     statement1; // Discarded if cond is false
else
     statement2; // Discarded if cond is true

For example:

template <typename T>
auto get_value(T t) {
    if constexpr (std::is_pointer_v<T>)
        return *t;
    else
        return t;
}

This removes a lot of the necessity for tag dispatching and SFINAE and even for #ifdefs.

I’d like to return to this feature when we are discussing features of C++17 that simplify the language. I hope to come back with more examples of constexpr if.

More details in:

MSVC 2017, GCC: 7.0, Clang: 3.9.

Other

In C++17, there are also other language features related to templates. In this post, I wanted to focus on the biggest enhancements, so I’ll just mention the others briefly:

  • Allow typename in a template's parameters: N4051.
    • Allows you to use typename instead of class when declaring a template parameter. Normal type parameters can use them interchangeably, but template parameters were restricted to class.
  • DR: Matching of template-arguments excludes compatible templates: P0522R0.
  • Allows constant evaluation for all non-type template arguments: N4268.
    • Removes syntactic restrictions for pointers, references, and pointers to members, that appear as non-type template parameters.
  • constexpr lambdas: P0170R1.
    • Lambda expressions may now be constant expressions.

Summary

Is there improved templates and meta-programming in C++17? Definitely!

We have really solid features like template deduction for class templates, template<auto>, plus some detailed features that fix some of the problems.

Still, for me, the most powerful features, that might have a significant impact on the code areconstexpr if and folds. They greatly clean up the code and make it more readable.

What are your favorite parts regarding templates?

Next time, we’ll address attributes like [[fallthrough]] or [[nodiscard]], and I’d like to recall other, already existing attributes. Stay tuned!

Deploying code to production can be filled with uncertainty. Reduce the risks, and deploy earlier and more often. Download this free guide to learn more. Brought to you in partnership with Rollbar.

Topics:
templates ,web dev ,c++17 ,code optimization

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}