Daniel Lemire's blog
Does C++ allow template specialization by concepts?

Recent versions of C++ (C++20) have a new feature: concepts. A concept in C++ is a named set of requirements that a type must satisfy. E.g., ‘act like a string’ or ‘act like a number’.  When used in conjunction with templates, concepts can be quite nice. Let us look at an example. Suppose that you want to define a generic function ‘clear’ which behaves some way on strings and some other way on other types. You might use the following code:
template <typename T>
void clear(T & t);

template <typename T>
concept not_string =
!std::is_same_v<T, std::string>;

template <>
void clear(std::string & t) {
  t.clear();
}

template <class T>
void clear(T& container) requires not_string<T> {
  for(auto& i : container) {
    i = typename T::value_type{};
  }
}


We have a generic clear function that takes a reference to any type T as an argument. We define a concept not_string which applies to any type expect std::string. We specialize for the std::string case: template <> void clear(std::string & t) { t.clear(); }: It directly calls the clear() member function of the string to empty its contents. Next we define template void clear(T& container) requires not_string { ... }: it is a generic implementation of clear for any type T that satisfies the not_string concept. It iterates over the container and sets each element to its default value. When you call clear with a std::string, the specialized version is used, directly calling std::string::clear(). For any other type that satisfies the not_string concept (i.e., is not a std::string), the generic implementation is used. It iterates over the container and sets each element to its default value. This assumes that T is a container-like type with a value_type and iterator support.

There are easier ways to achieve this result, but it illustrates the idea.

Up until recently, I thought that the template with the ‘requires’ keyword was a template specialization, just like when you specialize the template for the std::string type.

Unfortunately, it may be more complicated. Let us repeat exactly the same code but we put the clear function in a class ‘A’:
struct A {
    template <typename T>
    void clear(T & t);
};

template <>
void A::clear(std::string & t) {
  t.clear();
}

template <class T>
void A::clear(T& container) requires not_string<T> {
  for(auto& i : container) {
    i = typename T::value_type{};
  }
}


This time, your compiler might fail with the following or the equivalent:
out-of-line definition of 'clear' does not match any declaration in 'A'

You might fix the issue by adding the template with the constraint to the class definition.
struct A {
    template <typename T>
    void clear(T & t);
    template <class T>
    void clear(T& container) requires not_string<T>;
};

I find this unpleasant and inconvenient because you must put in your class declaration all of the concepts you plan on supporting. And if someone wants to support another concept, they need to change your class declaration to add it.

source
 
 
Back to Top