Simplifying a C++ Template with Concepts

Improving a previously created template with concepts

Updating the previous example

A few readers mentioned that they would use C++ Concepts (available in C++20) to accomplish the same thing as the previous example where we excluded the general case of a template by using partial specialization.

In this C++ code snippet, we’ll do just that.

LinkedInTable of Contents

Why bother? link icon

Although there are still reasons to understand the original design pattern (it’s useful to understand when doing template recursion), it’s true that concepts will improve the clarity of the code for the stated purpose: creating a template which permits only references and pointers (and not regular values).

The same thing, with Concepts link icon

As a reminder, the previous method accomplished this by omitting the generic template, and specifying only the reference and pointer template cases. This causes the compiler to throw an error anytime you use the template with a type that isn’t a reference or pointer, since it cannot find the implementation for the generic case (it does not exist).

We can do the same thing with concepts, by writing a concept which accepts pointer and reference types, and requiring it in the template.

Making the concept link icon

Creating a concept is simple. In this case there are some built in traits we can lean on to determine if a type parameter is a reference or pointer.

// Concept requires that the type be a pointer or reference
template <typename T>
concept IsRefOrPtr = std::is_reference_v<T> || std::is_pointer_v<T>;

Adding it to the template link icon

Updating the template to us the concept is simple. There are two styles of writing it. Both are equivalent

\Implicit “requires” (less typing)

template <IsRefOrPtr T>
class PtrRefOnlyValue {
public:
    explicit PtrRefOnlyValue(T p) : v(p) { }
private:
    T v;
};

\Alternative: explicit “requires” (more verbose)

template <typename T>
requires IsRefOrPtr<T>
class PtrRefOnlyValue {
public:
    explicit PtrRefOnlyValue(T p) : v(p) { }
private:
    T v;
};

\In use

void main() {
    int i = 17;
    auto* p = &i;
    auto& ref = i;

    PtrRefOnlyValue<int*> vp(p);    // ok
    PtrRefOnlyValue<int&> vr(ref);  // ok
    // PtrRefOnlyValue<int> vi(i);  // will not work
}

Benefits link icon

In addition to the code being shorter and more idiomatic for the next person to read and understand, should someone use the template the wrong way, the compiler output is far more helpful, and has a clear diagnostic statement. Templates can be difficult to understand, so anything which improves the clarity of diagnostic output is a win.

\ Diagnostic output is much improved

Cannot substitute type template argument int for constrained type
template parameter T because of violated constraint IsRefOrPtr<int>

Comments