Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Modern C++ Programming Cookbook

You're reading from   Modern C++ Programming Cookbook Master Modern C++ with comprehensive solutions for C++23 and all previous standards

Arrow left icon
Product type Paperback
Published in Feb 2024
Publisher Packt
ISBN-13 9781835080542
Length 816 pages
Edition 3rd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Marius Bancila Marius Bancila
Author Profile Icon Marius Bancila
Marius Bancila
Arrow right icon
View More author details
Toc

Table of Contents (15) Chapters Close

Preface 1. Learning Modern Core Language Features FREE CHAPTER 2. Working with Numbers and Strings 3. Exploring Functions 4. Preprocessing and Compilation 5. Standard Library Containers, Algorithms, and Iterators 6. General-Purpose Utilities 7. Working with Files and Streams 8. Leveraging Threading and Concurrency 9. Robustness and Performance 10. Implementing Patterns and Idioms 11. Exploring Testing Frameworks 12. C++ 20 Core Features 13. Other Books You May Enjoy
14. Index

Simplifying code with class template argument deduction

Templates are ubiquitous in C++, but having to specify template arguments all the time can be annoying. There are cases when the compiler can actually infer the template arguments from the context. This feature, available in C++17, is called class template argument deduction and enables the compiler to deduce the missing template arguments from the type of the initializer. In this recipe, we will learn how to take advantage of this feature.

How to do it...

In C++17, you can skip specifying template arguments and let the compiler deduce them in the following cases:

  • When you declare a variable or a variable template and initialize it:
    std::pair   p{ 42, "demo" };  // deduces std::pair<int, char const*>
    std::vector v{ 1, 2 };        // deduces std::vector<int>
    std::less   l;                // deduces std::less<void>
    
  • When you create an object using a new expression:
    template <class T>
    struct foo
    {
       foo(T v) :data(v) {}
    private:
       T data;
    };
    auto f = new foo(42);
    
  • When you perform function-like cast expressions:
    std::mutex mx;
    // deduces std::lock_guard<std::mutex>
    auto lock = std::lock_guard(mx);
    std::vector<int> v;
    // deduces std::back_insert_iterator<std::vector<int>>
    std::fill_n(std::back_insert_iterator(v), 5, 42);
    

How it works...

Prior to C++17, you had to specify all the template arguments when initializing variables, because all of them must be known in order to instantiate the class template, such as in the following example:

std::pair<int, char const*> p{ 42, "demo" };
std::vector<int>            v{ 1, 2 };
foo<int>                    f{ 42 };

The problem of explicitly specifying template arguments could have been avoided with a function template, such as std::make_pair(), which benefits from function template argument deduction and allows us to write code such as the following:

auto p = std::make_pair(42, "demo");

In the case of the foo class template shown here, we can write the following make_foo() function template to enable the same behavior:

template <typename T>
constexpr foo<T> make_foo(T&& value)
{
   return foo{ value };
}
auto f = make_foo(42);

In C++17, this is no longer necessary in the cases listed in the How it works... section. Let’s take the following declaration as an example:

std::pair p{ 42, "demo" };

In this context, std::pair is not a type, but it acts as a placeholder for a type that activates class template argument deduction. When the compiler encounters it during the declaration of a variable with initialization or a function-style cast, it builds a set of deduction guides. These deduction guides are fictional constructors of a hypothetical class type.

As a user, you can complement this set with user-defined deduction rules. This set is used to perform template argument deduction and overload resolution.

In the case of std::pair, the compiler will build a set of deduction guides that includes the following fictional function templates (but not only these):

template <class T1, class T2>
std::pair<T1, T2> F();
template <class T1, class T2>
std::pair<T1, T2> F(T1 const& x, T2 const& y);
template <class T1, class T2, class U1, class U2>
std::pair<T1, T2> F(U1&& x, U2&& y);

These compiler-generated deduction guides are created from the constructors of the class template, and if none are present, then a deduction guide is created for a hypothetical default constructor. In addition, in all cases, a deduction guide for a hypothetical copy constructor is always created.

The user-defined deduction guides are function signatures with a trailing return type and without the auto keyword (since they represent hypothetical constructors that don’t have a return value). They must be defined in the namespace of the class template they apply to.

To understand how this works, let’s consider the same example with the std::pair object:

std::pair p{ 42, "demo" };

The type that the compiler deduces is std::pair<int, char const*>. If we want to instruct the compiler to deduce std::string instead of char const*, then we need several user-defined deduction rules, as shown here:

namespace std {
   template <class T>
   pair(T&&, char const*)->pair<T, std::string>;
   template <class T>
   pair(char const*, T&&)->pair<std::string, T>;
   pair(char const*, char const*)->pair<std::string, std::string>;
}

These will enable us to perform the following declarations, where the type of the string "demo" is always deduced to be std::string:

std::pair  p1{ 42, "demo" };    // std::pair<int, std::string>
std::pair  p2{ "demo", 42 };    // std::pair<std::string, int>
std::pair  p3{ "42", "demo" };  // std::pair<std::string, std::string>

As you can see from this example, deduction guides do not have to be function templates.

It is important to note that class template argument deduction does not occur if the template argument list is present, regardless of the number of specified arguments. Examples of this are shown here:

std::pair<>    p1 { 42, "demo" };
std::pair<int> p2 { 42, "demo" };

Because both these declarations specify a template argument list, they are invalid and produce compiler errors.

There are some known cases where class template argument deduction does not work:

  • Aggregate templates, where you could write a user-defined deduction guide to circumvent the problem.
    template<class T>
    struct Point3D { T x; T y; T z; }; 
     
    Point3D p{1, 2, 2};   // error, requires Point3D<int>
    
  • Type aliases, as shown in the following example (for GCC, this actually works when compiling with -std=c++20):
    template <typename T>
    using my_vector = std::vector<T>;
    std::vector v{1,2,3}; // OK
    my_vector mv{1,2,3};  // error
    
  • Inherited constructors, because deduction guides, whether implicit or user-defined, are not inherited when constructors are inherited:
    template <typename T> 
    struct box
    {
       box(T&& t) : content(std::forward<T>(t)) {}
       virtual void unwrap()
       { std::cout << "unwrapping " << content << '\n'; }
       T content;
    };
    template <typename T>
    struct magic_box : public box<T>
    {
       using box<T>::box;
       virtual void unwrap() override
       { std::cout << "unwrapping " << box<T>::content << '\n'; }
    };
    int main()
    {
       box b(42);        // OK
       b.unwrap();
       magic_box m(21);  // error, requires magic_box<int>
       m.unwrap();
    }
    

This latter limitation has been removed in C++23, where deduction guides are inherited when constructors are inherited.

See also

  • Understanding uniform initialization, to see how brace-initialization works
You have been reading a chapter from
Modern C++ Programming Cookbook - Third Edition
Published in: Feb 2024
Publisher: Packt
ISBN-13: 9781835080542
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime