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:
- 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.
- 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.
- 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.
- 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.
- 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.
- 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));
}
- 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.
- 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));
}
- 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.
- 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'));
}
- 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.
- 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
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.