Check Types with Concepts
Concepts are a powerful and elegant tool to check at compile time if a type fulfills. Thanks to static_assert
, you can use concepts as a standalone feature: static_assert(Concept<T>)
.
I often have the question in my C++ class: How can I be sure that my data type is moveable? Well, you can either study the dependencies between the Big Six or define and use the concept Big Six. In my last post, “Check Types with Concepts – The Motivation“, I presented the first part of the answer and explained the very sophisticated dependencies between the Big Six. As a reminder, here are the Big Six, including move semantics:
- Default constructor:
X()
- Copy constructor:
X(const X&)
- Copy assignment:
operator = (const X&)
- Move constructor:
X(X&&)
- Move assignment:
operator = (X&&)
- Destructor:
~(X)
Today, I want to define and use the concept Big Six.
Before I do that, I have a short disclaimer: C++20 already supports the concepts std::semiregular
and std::regular
.
std::semiregular
and std::regular
A semiregular type has to support the Big Six and must be swappable:
- Default constructor:
X()
- Copy constructor:
X(const X&)
- Copy assignment:
operator = (const X&)
- Move constructor:
X(X&&)
- Move assignment:
operator = (X&&)
- Destructor:
~(X)
- Swappable:
swap(X&, X&)
Additionally, std::regular
requires for a type X
that it supports the concept std::semiregular
and is equality comparable.
- Default constructor:
X()
- Copy constructor:
X(const X&)
- Copy assignment:
operator = (const X&)
- Move constructor:
X(X&&)
- Move assignment:
operator = (X&&)
- Destructor:
~(X)
- Swappable:
swap(X&, Y&)
- Equality comparable:
bool operator == (const X&, const X&)
That said, there is essentially no reason to define the concept BigSix. Just use the concept std::semiregular,
because you get the swappable property for free. Here is a C++11 implementation of std::swap
:
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
template <typename T> void swap(T& a, T& b) noexcept { T tmp(std::move(a)); // move constructor a = std::move(b); // move assignment b = std::move(tmp); // move assignment }
When you invoke swap(a, b)
, the compiler applies move semantics to its arguments a
and b
. Consequentially, a type supporting the concept BigSix also supports swappable and, therefore, supports the concept std::semiregular
.
Now, let me implement the concept BigSix.
The Concept BigSix
Thanks to the type traits functions, implementing BigSix is a no-brainer. In the first step, I define the type traits isBigSix
, and in the second step, I use it directly to define the concept BigSix
. Here we are:
// bigSixConcept.cpp #include <algorithm> #include <iostream> #include <type_traits> template<typename T> struct isBigSix: std::integral_constant<bool, std::is_default_constructible<T>::value && std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value && std::is_move_constructible<T>::value && std::is_move_assignable<T>::value && std::is_destructible<T>::value>{}; template<typename T> concept BigSix = isBigSix<T>::value; template <BigSix T> // (1) void swap(T& a, T& b) noexcept { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); } struct MyData{ // (2) MyData() = default; MyData(const MyData& ) = default; MyData& operator=(const MyData& m) = default; }; int main(){ std::cout << '\n'; MyData a, b; swap(a, b); // (3) static_assert(BigSix<MyData>, "BigSix not supported"); // (4) std::cout << '\n'; }
Now, my function swap
requires that the type parameter T supports BigSix (line 1). In line 3, I invoke the function swap
with arguments of type MyData
. Additionally, I explicitly check in line 4 if MyData
supports the concept BigSix
. MyData
(line 2) has a default constructor and supports copy semantics. The program can be compiled and executed.
Does this mean that MyData
supports the concept BigSix
and is, therefore, moved inside my function swap
? Yes, MyData
supports the concept BigSix, but no, MyData
is not moved inside my function swap
. Copy semantic kicks in as a fallback for move semantics.
Here is a slightly modified program.
// bigSixConceptComments.cpp #include <algorithm> #include <iostream> #include <type_traits> template<typename T> struct isBigSix: std::integral_constant<bool, std::is_default_constructible<T>::value && std::is_copy_constructible<T>::value && std::is_copy_assignable<T>::value && std::is_move_constructible<T>::value && std::is_move_assignable<T>::value && std::is_destructible<T>::value>{}; template<typename T> concept BigSix = isBigSix<T>::value; template <BigSix T> void swap(T& a, T& b) noexcept { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); } struct MyData{ MyData() = default; MyData(const MyData& ) { std::cout << "copy constructor\n"; } MyData& operator=(const MyData& m) { std::cout << "copy assignment operator\n"; return *this; } }; int main(){ std::cout << '\n'; MyData a, b; swap(a, b); static_assert(BigSix<MyData>, "BigSix not supported"); std::cout << '\n'; }
I added comments to the copy constructor and copy assignment operator of MyData
. Executing the program shows that both special member functions are used:
By the way, this observation is already documented in cppreference.com. For example, a note about the type trait std::is_move_constructible states: “Types without a move constructor, but with a copy constructor that accepts const T& arguments, satisfy std::is_move_constructible.“
Okay, we are back to square one. We can decide if a type supports BigSix
, but we cannot decide if a type is moved. If you want to know if your type supports move semantics and not that copy semantics is used as a fallback for move semantics, you must study the dependency table of my previous post: “Check Types with Concepts – The Motivation“.
What’s next?
In my next post, I want to continue my story with ranges. Additionally, ranges will get many improvements in C++23.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!