Book Image

Advanced C++

By : Gazihan Alankus, Olena Lizina, Rakesh Mane, Vivek Nagarajan, Brian Price
5 (1)
Book Image

Advanced C++

5 (1)
By: Gazihan Alankus, Olena Lizina, Rakesh Mane, Vivek Nagarajan, Brian Price

Overview of this book

C++ is one of the most widely used programming languages and is applied in a variety of domains, right from gaming to graphical user interface (GUI) programming and even operating systems. If you're looking to expand your career opportunities, mastering the advanced features of C++ is key. The book begins with advanced C++ concepts by helping you decipher the sophisticated C++ type system and understand how various stages of compilation convert source code to object code. You'll then learn how to recognize the tools that need to be used in order to control the flow of execution, capture data, and pass data around. By creating small models, you'll even discover how to use advanced lambdas and captures and express common API design patterns in C++. As you cover later chapters, you'll explore ways to optimize your code by learning about memory alignment, cache access, and the time a program takes to run. The concluding chapter will help you to maximize performance by understanding modern CPU branch prediction and how to make your code cache-friendly. By the end of this book, you'll have developed programming skills that will set you apart from other C++ programmers.
Table of Contents (11 chapters)
7
6. Streams and I/O

Chapter – 2B - No Ducks Allowed – Templates and Deduction

Activity 1: Developing a Generic "contains" Template Function

In this activity, we will implement several helper classes that will be used to detect the std::string class case and the std::set case and then use them to tailor the contains function to the particular container. Follow these steps to implement this activity:

  1. Load the prepared project from the Lesson2B/Activity01 folder. Build and configure the launcher and run the unit tests (which fail the one dummy test). We recommend that the name that's used for the tests runner is L2BA1tests.
  2. Open the containsTests.cpp file and replace the existing test with the following:

    TEST_F(containsTest, DetectNpos)

    {

        ASSERT_TRUE(has_npos_v<std::string>);

        ASSERT_FALSE(has_npos_v<std::set<int>>);

        ASSERT_FALSE(has_npos_v<std::vector<int>>);

    }

    This test requires us to write a set of helper templates to detect if the container class supports a static member variable called npos.

  3. Add the following code to the contains.hpp file:

    template <class T>

    auto test_npos(int) -> decltype((void)T::npos, std::true_type{});

    template <class T>

    auto test_npos(long) -> std::false_type;

    template <class T>

    struct has_npos : decltype(test_npos<T>(0)) {};

    template< class T >

    inline constexpr bool has_npos_v = has_npos<T>::value;

    The tests now run and pass.

  4. Add the following tests to the containsTest.cpp file:

    TEST_F(containsTest, DetectFind)

    {

        ASSERT_TRUE((has_find_v<std::string, char>));

        ASSERT_TRUE((has_find_v<std::set<int>, int>));

        ASSERT_FALSE((has_find_v<std::vector<int>, int>));

    }

    This test requires us to write a set of helper templates to detect if the container class has a find() method that takes one argument.

  5. Add the following code to the contains.hpp file:

    template <class T, class A0>

    auto test_find(int) ->

           decltype(void(std::declval<T>().find(std::declval<A0>())),

                                                            std::true_type{});

    template <class T, class A0>

    auto test_find(long) -> std::false_type;

    template <class T, class A0>

    struct has_find : decltype(test_find<T,A0>(0)) {};

    template< class T, class A0 >

    inline constexpr bool has_find_v = has_find<T, A0>::value;

    The tests now run and pass.

  6. Add the implementation for the generic container; in this case, the vector. Write the following tests in the containsTest.cpp file:

    TEST_F(containsTest, VectorContains)

    {

        std::vector<int> container {1,2,3,4,5};

        ASSERT_TRUE(contains(container, 5));

        ASSERT_FALSE(contains(container, 15));

    }

  7. Add the basic implementation of contains to the contains.hpp file:

    template<class C, class T>

    auto contains(const C& c, const T& key) -> decltype(std::end(c), true)

    {

            return std::end(c) != std::find(begin(c), end(c), key);

    }

    The tests now run and pass.

  8. The next step is to add the tests for the set special case to containsTest.cpp:

    TEST_F(containsTest, SetContains)

    {

        std::set<int> container {1,2,3,4,5};

        ASSERT_TRUE(contains(container, 5));

        ASSERT_FALSE(contains(container, 15));

    }

  9. The implementation of contains is updated to test for the built-in set::find() method:

    template<class C, class T>

    auto contains(const C& c, const T& key) -> decltype(std::end(c), true)

    {

        if constexpr(has_find_v<C, T>)

        {

            return std::end(c) != c.find(key);

        }

        else

        {

            return std::end(c) != std::find(begin(c), end(c), key);

        }

    }

    The tests now run and pass.

  10. Add the tests for the string special case to the containsTest.cpp file:

    TEST_F(containsTest, StringContains)

    {

        std::string container{"This is the message"};

        ASSERT_TRUE(contains(container, "the"));

        ASSERT_TRUE(contains(container, 'm'));

        ASSERT_FALSE(contains(container, "massage"));

        ASSERT_FALSE(contains(container, 'z'));

    }

  11. Add the following implementation of contains to test for the presence of npos and tailor the use of the find() method:

    template<class C, class T>

    auto contains(const C& c, const T& key) -> decltype(std::end(c), true)

    {

        if constexpr(has_npos_v<C>)

        {

            return C::npos != c.find(key);

        }

        else

        if constexpr(has_find_v<C, T>)

        {

            return std::end(c) != c.find(key);

        }

        else

        {

            return std::end(c) != std::find(begin(c), end(c), key);

        }

    }

    The tests now run and pass.

  12. Build and run the application called contains. Create a new Run Configuration. If your implementation of the contains template is correct, then the program will display the following output:
Figure 2B.36: Output from the successful implementation of contains
Figure 2B.36: Output from the successful implementation of contains

In this activity, we used various templating techniques in conjunction with SFINAE to select the appropriate implementation of a contains() function based upon the capability of the containing class. We could have achieved the same result using a generic template function and some specialized templates, but we took the path less travelled and flexed our newly found template skills.