This is my first attempt to experiment with concepts in C++, and yes, it’s amazing. So far, most of us we use templates, and this helps us to write modular and clean code without repeating ourselves. It’s always nice to start with a simple example and say that we want to write a function that finds the minimum of an array.
template T min(T* values, size_t length) { T min{values[0]}; for (size_t i{1}; i<length; ++i) if (values[i] < min) min = values[i]; return min; }
But do we know if that template will work for any template arguments? No, we don’t
How can be sure?
What if we had the opportunity to check the template arguments to discover if they meet specific criteria to be used with the given template?
The concept is like a predicate that evaluates to true if those parameters can be used of false if not and the template instantiation will fail, as it should, at compile time.
When you work with concepts, you have many friends. One of those is type traits. Using them, you can inspect type properties like if this type is integral or default constructible and so on. You can access them using the header file.
So for example, if we want to check a type to see if it is bool, char type, int type, short type, long type, or long long type, we just use std::is_integral::value which as we expect is true.
Enough with the words, let’s see some actions. Now it is time to write our first concept.
By inspecting the function code, I think that we should ensure that the template argument should comply with the following rules:
– Be constructible because of T min{values[0]}
– Be comparable because of values[i] < min
– Be copy constructible because of min = values[i]
how this will look like?
template<typename T> concept bool Minable() { return std::is_constructible<T>::value && std::is_copy_constructible<T>::value && requires (T a, T b) { { a < b } -> T; }; }
Hmm, this is my concept called Minable (yes I don’t like the name either!)
and it returns a boolean value. It engages 2 type traits and one requirement.
What is the requirement? It’s a custom constraint on the template parameters. It is actually an expression with a body and return type like that:
{ expression } -> return-Type;
so in our case, we want the type T to be comparable and the result to be again type T.
And how do we use it? This is very simple since the only thing we should do is to replace the typename inside <> like that.
template<Minable T> T min(T* values, size_t length) { T min{values[0]}; for (size_t i{1}; i<length; ++i) if (values[i] < min) min = values[i]; return min; }
In case you want to experiment, please use CMake with the following CMakeLists.txt:
cmake_minimum_required(VERSION 3.10) project(6_concepts) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fconcepts") add_executable(6_concepts main.cpp)
and the full code:
#include <iostream> #include <type_traits> #include <cstddef> template<typename T> concept bool Minable() { return std::is_constructible<T>::value && std::is_copy_constructible<T>::value && requires (T a, T b) { { a < b } -> T; }; } template<Minable T> T min(T* values, size_t length) { T min{values[0]}; for (size_t i{1}; i<length; ++i) if (values[i] < min) min = values[i]; return min; } int main() { double nums_d[]{10.0f, 2.0f, 3.0f, 4.0f }; auto result = min(nums_d, 4); std::cout << result << std::endl; return 0; }