Practice makes perfect, part 3 – Idiomatic kata

Previously, we looked at some of the foundational C++ code kata – that is, elements of C++ coding that are absolutely key to master if you’re going to be programming in C++.
Practice makes perfect, part 1 – Code kata
Practice makes perfect, part 2 – foundation kata

In this article I want to introduce what I call ‘idiomatic’ kata.  These exercises have a bit more latitude (and variation) in how they can be implemented.  In that respect they are closer to traditional code kata.  The idea with these kata is to reinforce C++ constructs that aren’t encountered quite so often in typical C++ programming and so are more easily forgotten (or even avoided)

There’s no order to these kata.  None is more important than any other.

If you’re new to C++ there’s nothing wrong with practicing these exercises; but use them as a means to explore new language features.  It’s good to learn how these idiomatic patterns work.

pImpl idiom

Why is this important?

Breaks dependencies between users of a class and its implementation

Example

#include <memory>


class ADT {
public:
    ADT();

    void op_A();
    void op_B();

private:
    class Impl;
    std::unique_ptr<Impl> impl;
};


ADT::ADT() : impl { std::make_unique<ADT::Impl>() }
{
}


class ADT::Impl {
public:
    void op_A() { /* ... */ }
    void op_B() { /* ... */ }
};


void ADT::op_A()
{
    impl->op_A();
}


void ADT::op_B()
{
    impl->op_B();
}


int main()
{
    ADT adt { };
    adt.op_A();
}

 

Object adapter

Why is this important?

Provides a new interface for an existing utility

Read more here.

Variations

  • Use interfaces for existing API and required API

Example

class Service {
public:
    virtual ~Service() = default;

    virtual void service1() = 0;
    virtual void service2() = 0;
};


class Utility {
public:
  void func1() { /* ... */ }
  void func2() { /* ... */ }

protected:
  void helper_function() { /* ... */ }
};


class Client {
public:
    void run();

private:
    Service* server { nullptr };
    friend void connect(Client& client, Service& srv);
};


void Client::run()
{
    server->service1();
}


void connect(Client& client, Service& srv)
{
    client.server = &srv;
}


class Object_adapter : public Service {
protected:
    void service1() override { utility.func1(); }
    void service2() override { utility.func2(); }

private:
    Utility utility { };
};


int main()
{
    Client client { };
    Object_adapter adapter { };
    connect(client, adapter);

    client.run();
}

 

Class adapter

Why is this important?

Provides a new interface for an existing utility

Practical application of private inheritance

Useful for providing ‘subset’ interfaces of existing types

Simulates derived (non-substitutable) type, versus sub-typing (substitution)

Variations

  • Use interfaces for existing API and required API

Example

#include <vector>


enum class Event { advisory, caution, warning };


class Event_list : private std::vector<Event> {
public:
    using Base = std::vector<Event>;

    // Modified API
    //
    void add(Event e) { Base::push_back(e); }

    // Subset API
    //
    using Base::Base;
    using Base::begin;
    using Base::end;
    using Base::size;
};


int main()
{
    Event_list events {
        Event::warning,
        Event::warning,
        Event::caution
    };

    for (auto e : events) {
        // ...
    }
}

 

Circular buffer

Why is this important?

A fixed-size FIFO is not part of the STL

Very commonly used in embedded systems

A nice exercise for manipulation of iterators on containers

Variations

  • Throw exceptions on empty/full
  • Return tuple from ‘get’ function
  • Return std::optional from ‘get’ function
  • Support ‘add’ by move and copy
  • Support ‘add’ by move and copy via perfect forwarding
  • Make thread-safe (using an adapter)

Example

#include <cstddef>
#include <array>
#include <optional>


template<typename T = int, std::size_t sz = 8>
class Buffer {
public:
    Buffer() = default;

    template <typename U>
    bool add(U&& in_val);

    std::optional<T>   get();
    inline bool        is_empty() const;
    inline std::size_t size() const;

private:
    using Container = std::array<T, sz>;
    using Iterator  = typename Container::iterator;

    Container buffer    {  };
    Iterator  read      { std::begin(buffer) };
    Iterator  write     { std::begin(buffer) };
    unsigned  num_items { 0 };
};


template<typename T, std::size_t sz>
template<typename U>
bool Buffer<T, sz>::add(U&& in_val)
{
    if (num_items == sz) return false;

    *write = std::forward<U>(in_val);
    ++num_items;
    ++write;
    if (write == std::end(buffer)) write = std::begin(buffer);

    return true;
}


template<typename T, std::size_t sz>
std::optional<T> Buffer<T, sz>::get()
{
    if (num_items == 0) return std::nullopt;

    auto selected = read;
    --num_items;
    ++read;
    if (read == std::end(buffer)) read = std::begin(buffer);
    return std::move(*selected);
}


template<typename T, std::size_t sz>
bool Buffer<T, sz>::is_empty() const
{
    return (num_items == 0);
}


template<typename T, std::size_t sz>
std::size_t Buffer<T, sz>::size() const
{
    return num_items;
}

 

Port

Why is this important?

Implementation of UML ports

Allows a class to realize multiple interfaces without multiple inheritance

For more information have a look at this article.

Variations

  • Delegate to an internal implementation

 

Example

#include <iostream>

using namespace std;


class Interface_1 {
public:
    virtual ~Interface_1() = default;
    virtual void op() = 0;
};


class Interface_2 {
public:
    virtual ~Interface_2() = default;
    virtual void op() = 0;
};


// Class Implementation cannot realize
// both interfaces, so use a port for
// each interface
//
class Implementation {
private:
    class Port_1 : public Interface_1 {
    protected:
        void op() override
        {
            cout << "Port_1 realization" << endl;
        }
    };

    class Port_2 : public Interface_2 {
    protected:
        void op() override
        {
            cout << "Port_2 realization" << endl;
        }
    };

public:
    // Port objects
    //
    Port_1 port_1 { };
    Port_2 port_2 { };
};


class Client_1 {
public:
    void run() { server->op(); }

private:
    Interface_1* server { nullptr };
    friend void connect(Client_1& client, Interface_1& srv);
};


void connect(Client_1& client, Interface_1& srv)
{
    client.server = &srv;
}


class Client_2 {
public:
    void run() { server->op(); }

private:
    Interface_2* server { nullptr };
    friend void connect(Client_2& client, Interface_2& srv);
};


void connect(Client_2& client, Interface_2& srv)
{
    client.server = &srv;
}


int main()
{
    Implementation impl { };
    Client_1 client_1 { };
    Client_2 client_2 { };

    connect(client_1, impl.port_1);
    connect(client_2, impl.port_2);

    client_1.run();
    client_2.run();
}

 

Copy-swap idiom

Why is this important?

Idiomatic implementation for deep-copy and move-semantics on resource-owning types.

Variations

  • Implement move-only types

Example

 

#include <iostream>
#include <cstring>


class Simple_string {
public:
    Simple_string() = default;
    Simple_string(const char* str);

    friend std::ostream& operator<<(
        std::ostream& os, 
        const Simple_string& str
    );

    // Copy and move policy (deep-copy)
    //
    ~Simple_string();
    Simple_string(const Simple_string& other);
    Simple_string(Simple_string&& other) noexcept;
    Simple_string& operator=(Simple_string rhs);

    friend void swap(Simple_string& lhs, Simple_string& rhs) noexcept;

private:
    char* raw { nullptr };
};


Simple_string::Simple_string(const char* str)
{
    if (str) {
        auto num_chars = strlen(str);
        raw = new char[ num_chars + 1 ];
        strncpy(raw, str, num_chars);
    }
}


Simple_string::~Simple_string()
{
    delete[] raw;
}


Simple_string::Simple_string(const Simple_string& other) :
    Simple_string { other.raw }
{
}


Simple_string::Simple_string(Simple_string&& other) noexcept :
    Simple_string { }
{
    swap(*this, other);
}


Simple_string& Simple_string::operator=(Simple_string rhs)
{
    swap(*this, rhs);
    return *this;
}


std::ostream& operator<<(std::ostream& os, const Simple_string& str)
{
    if (str.raw) os << str.raw;
    return os;
}


void swap(Simple_string& lhs, Simple_string& rhs) noexcept
{
    using std::swap;
    swap(lhs.raw, rhs.raw);
}


int main()
{
    Simple_string s1 { };
    Simple_string s2 { "Hello world" };

    s1 = s2;

    std::cout << s1 << std::endl;
    std::cout << s2 << std::endl;
}

 

Variadic template function

Why is this important?

Reinforces syntax of variadic templates

Variations

  • Add a non-variadic template parameter
  • Add a non-template-type parameter

Example

#include <iostream>

using namespace std;


template <typename T, typename... Other_Ty>
void print(T&& first, Other_Ty&&... others)
{
    cout << first << " ";
    print(std::forward<Other_Ty>(others)...);
}


// Overload with no parameters  to
// terminate template recursion
//
void print()
{
    cout << endl;
}


int main()
{
    print(1, 2, 14.7);
    print(34.5);
    print();
}

 

Variadic base classes

Why is this important?

Reinforces syntax of variadic templates

Useful implementation for mix-in classes

For an application of this technique, have a look at this article.

Variations

  • Expose base class member functions

Example

#include <iostream>

using namespace std;


template <typename... Mixin_Ty>
class Utility : public Mixin_Ty... {
public:
    using Mixin_Ty::operator()...;
};


class Feature1 {
public:
    void operator()(int val) 
    { 
        cout << "Feature1 " << val << endl; 
    }
};


class Feature2 {
public:
    void operator()() 
    { 
        cout << "Feature 2" << endl; 
    }
};


int main()
{
    Utility<Feature1, Feature2> utility { };

    utility();
    utility(100);
}

 

Class template deduction guide

Why is this important?

Simplifies template class code

Replaces factory functions in a lot of cases

Reinforces syntax.

For more information have a look at this article.

Variations

  • Deduction guide for class with no constructor
  • Deduction guide for class constructor with reference parameters
  • Non-template-type deduction guide

Example

#include <iostream>

using namespace std;


template <typename T>
class ADT {
public:
    ADT(const T& init) : value { init }
    {
    }

    void show_type() const
    {
        cout << typeid(value).name() << endl;
    }

private:
    T value { };
};


// Deduction guide
//
template <typename T> ADT(T) -> ADT<T>;


// Explicit deduction guide for
// string literals.
//
ADT(const char*) -> ADT<std::string>;


int main()
{
    ADT adt1 { 55 };        // T => int     
    ADT adt2 { 12.7 };      // T => double     
    ADT adt3 { "Hello" };   // T => std::string

    adt1.show_type();
    adt2.show_type();
    adt3.show_type();
}

 

Forwarding reference idiom

Why is this important?

Forms the basis of ‘make’ functions

Allows ‘by-copy’ and ‘by-move’ variations of functions

Variations

  • Use a variadic template function

Example

#include <iostream>

using namespace std;


class ADT {
public:
    ADT(int init) : value { init }
    {
    }

    void op() const
    {
        cout << value << endl;
    }

private:
    int value { };
};


template <typename Made_Ty, typename... Param_Ty>
Made_Ty make_thing(Param_Ty&&... arg)
{
    return Made_Ty { std::forward<Param_Ty>(arg)... };
}


int main()
{
    auto val1 = make_thing<double>(100.7);
    auto val2 = make_thing<ADT>(100);
}

 

Curiously Recurring Template Pattern

Why is this important?

CRTP is a counter-intuitive usage of templates.

Allows a template implementation of ‘static polymorphism’

Template implementation of ‘method chaining’

Variations

  • Implement generic object counter
  • Implement generic ABC
  • Implement polymorphic chaining

Example

#include <iostream>


using namespace std;


template <typename Derived_Ty>
class Base {
public:
    void op1()  { actual().op1_impl(); }
    void op2()  { actual().op2_impl(); }

private:
    void op1_impl() { cout << "Base op1" << endl; }
    void op2_impl() { cout << "Base op2" << endl; }

    // Helper function to improve readability
    //
    Derived_Ty& actual()
    {
        return *(static_cast<Derived_Ty*>(this));
    }
};


class Derived : public Base<Derived> {
public:
    void op1() { cout << "Derived op1" << endl; }
};


class Another : public Base<Derived> {
public:
    void op2() { cout << "Another op2" << endl; }
};


int main()
{
    Derived d1 { };
    d1.op1();
    d1.op2();

    Another a1 { };
    a1.op1();
    a1.op2();
}

 

Template policy

Why is this important?

Template replacement for client-server association

For more information have a look at this article.

Variations

  • Client creates server object (composition)
  • Client has association to object

Example

#include <iostream>

using namespace std;


template <typename Server_Ty>
class Client {
public:
    Client(Server_Ty& srv) : server { &srv}
    {
    }

    void run()
    {
        // Contractual obligation to support
        // do_stuff();
        //
        server->do_stuff();
    }

private:
    Server_Ty* server { nullptr };
};


// Template deduction guide allows Server_Ty
// to be deduced as the association between client
// and server is formed (via the ctor)
//
template <typename T>
Client(T) -> Client<T>;


class ADT {
public:
    int do_stuff()
    {
        cout << "ADT" << endl;
        return 0;
    }
};


class Another {
public:
    void do_stuff() { cout << "Hello" << endl; }
    void do_more()  { cout << "World" << endl; }
};


int main()
{
    Another server { };
    Client  client { server };

    client.run();
}

 

Class template specialisation

Why is this important?

Provide specialized implementations for particular types

Variations

  • Explicit specialization
  • Partial specialization

Example

#include <iostream>

using namespace std;


template <typename T>
class Processor {
public:
    Processor(const T& init) : value { init}
    {
    }

    void process()
    {
        cout << "Base implementation" << endl;
    }

private:
    T value { };
};


template <typename T>
Processor(T) -> Processor<T>;


template <typename T>
class Processor<T*> {
public:
    Processor(T* init) : value { init}
    {
    }

    void process()
    {
        cout << "Pointer implementation" << endl;
    }

private:
    T* value { };
};


template <typename T>
Processor(T*) -> Processor<T*>;


template <>
class Processor<std::string> {
public:
    Processor(std::string init) : value { std::move(init) }
    {
    }

    void process()
    {
        cout << "std::string implementation" << endl;
    }

private:
    std::string value { };
};


Processor(const char*) -> Processor<std::string>;


int main()
{
    Processor p1 { 100 };
    p1.process();

    int i;
    Processor p2 { &i };
    p2.process();

    Processor p3 { "Hello, world!" };
    p3.process();
}

 

Function template specialisation – The Dimov/Abrahams example

Why is this important?

Understanding the Dimov/Abrahams example for function template specialization is the key to knowing why you shouldn’t ever specialize template functions!

See Herb Sutter’s article for more detail.

Variations

  • Overload with non-template functions

Example

#include <iostream>

using namespace std;


template <typename T>
void process(T param)
{
    cout << "process for T" << endl;
}


// Placed here, this becomes a specialization
// of void process(T)
//
// template <>
// void process<>(int* param)
// {
//     cout << "Explicit specialization for int*" << endl;
// }

template <typename T>
void process(T* param)
{
    cout << "specialization for T*" << endl;
}


// Placed here, this becomes a specialization
// of void process<>(int*)
//
template <>
void process<>(int* param)
{
    cout << "Explicit specialization for int*" << endl;
}


int main()
{
    int i { };
    process(i);
    process(&i);
}

 

enable_if

Why is this important?

Conditional compilation idiom for template functions

For more information, and application, of this technique have a look at this article.

Variations

  • SFINAE via invalid template parameter
  • SFINAE via invalid return type
  • SFINAE via invalid function parameter
  • Use type alias to improve code readability

Example

#include <iostream>
#include <type_traits>

using namespace std;


// Aliases to aid readability
//
template <typename T>
using is_floating_type = 
    std::enable_if<std::is_floating_point<T>::value, bool>::type;

template <typename T>
using is_other_type = 
    std::enable_if<!std::is_floating_point<T>::value, bool>::type;


// A - only enabled for floating-point types
//
template <typename T, is_floating_type<T> = true>
void process(T param)
{
    cout << param << " is a floating point type" << endl;
}


// B - Enabled for all non-floating-point types
//
template <typename T, is_other_type<T> = true>
void process(T param)
{
    cout << param << " is some other type" << endl;
}


int main()
{
    process(100);  // calls (B)
    process(17.6); // calls (A)
}

 

Summary

Repetition is the key to competence.  More importantly, you should practice the things you are bad at, the things you find most difficult, more often.

This set of kata represent a small set of all the possible exercises you could (and should) practice in C++.  I’m sure you have your own.  I’d be interested to hear the kata you regularly practice.

Glennan Carnie
Dislike (0)
Website | + posts

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry.

He specialises in C++, UML, software modelling, Systems Engineering and process development.

About Glennan Carnie

Glennan is an embedded systems and software engineer with over 20 years experience, mostly in high-integrity systems for the defence and aerospace industry. He specialises in C++, UML, software modelling, Systems Engineering and process development.
This entry was posted in C/C++ Programming, Design Issues, training and tagged , , , . Bookmark the permalink.

Leave a Reply