Tuesday coding tips are super short posts about various tidbits mainly from C++, but also from other programming languages I use. You can also follow the #TuesdayCodingTips hashtag on Mastodon and Linkedin.
What if you had a template class and you wanted to give it extra methods for certain specializations?
For example, you might want to make a templated initialization guard and you want it to be as non-intrusive as possible. So you want it to support arithmetic operations if the underlying type is an arithmetic type.
Before C++20, you could leverage std::enable_if. However, such code is clunky and unreadable:
#include <type_traits>
#include <string>
// Pre C++20
template<class T>
class TemplatedClass
{
public:
template<class = typename std::enable_if<std::is_integral<T>::value>::type>
void foo() {}
};
int main()
{
TemplatedClass<int> a;
a.foo(); // OK
TemplatedClass<std::string> b;
b.foo(); /* Error:
error: failed requirement 'std::is_integral<std::basic_string...>' requested here
error: no matching member function for call to 'foo'
note: candidate template ignored: couldn't infer template argument ''
*/
}
With C++20, you can use concepts and the requires keyword to clean up the syntax significantly, either by in-place definition of type properties or by defining a dedicated concept. It also improves readability of error messages.
#include <type_traits>
#include <string>
// C++20, requires
template<class T>
class TemplatedClass2
{
public:
template<typename = void>
requires std::is_integral_v<T>
void foo() {}
};
// C++20 with concept
template<class T>
concept IsIntegral = std::is_integral_v<T>;
template<class T>
class TemplatedClass3
{
public:
template<typename = void>
requires IsIntegral<T>
void foo() {}
};
int main() {
TemplatedClass2<int> a;
a.foo(); // Ok
TemplatedClass3<std::string> b;
b.foo(); /* Error:
error: no matching member function for call to 'foo'
note: candidate template ignored: constraints not satisfied [with $0 = void]
note: because 'std::basic_string<char>' does not satisfy 'IsIntegral'
note: because 'std::is_integral_v<std::basic_string<char> >' evaluated to false
*/
}