Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
Comments on Pros and cons of various type_traits idioms
Post
Pros and cons of various type_traits idioms
My work tasks have recently started requiring me to use the type_traits
header to restrict the classes that may be used in template functions, methods, and classes. And while I used it for a long time now, I learned C++ on the job.
I've seen at least four patterns (see below) for actually coding these things and don't know what (if any) reasons are there for preferring one idiom.
I describe the four idioms I've seen as
-
enable_if
on the return type (functions and methods) - Flow control or
static_assert
on type traits. (functions and methods) -
enable_if
intypedef
orusing
statement (can be used in functions and methods, but I think it is more common with classes) - For more complex traits (like
interator_traits
) you can use tag dispatch to a separate implementation
What are the pros and cons of these options? Is there a developer consensus on which are clearer or more maintainable?
So example code (in C++11) showing the idioms I've encountered (the bodies of the functions are unimportant here, but resemble simplified versions of things that have come up at work).
#include <cmath>
#include <iterator>
#include <list>
#include <type_traits>
#include <vector>
// Method 1: enable_if on return type
template <typename T>
typename std::enable_if<std::is_floating_point<T>::value, T>::type
almostEqual1(const T f1, const T f2, const T epsilon = 1e-6)
{
return std::fabs(f1-f2) < static_cast<T>(epsilon);
}
// Method 2: if (constexpr ...) or static_assert on a type trait
template <typename T>
bool almostEqual2(const T f1, const T f2, const T epsilon = 1e-6)
{
static_assert( std::is_floating_point<T>::value,
"Arguments must be of floating point type" );
return std::fabs(f1-f2) < static_cast<T>(epsilon);
}
// Method 3: using with enable_if
// Perhaps used more with template classes?
template <typename T>
bool almostEqual3(const T f1, const T f2, const T epsilon = 1e-6)
{
using enable = typename
std::enable_if<std::is_floating_point<T>::value, void>::type;
return std::fabs(f1-f2) < static_cast<T>(epsilon);
}
// Method 4 (iterators or customm traits): tag dispatching with separate implementation
template <typename Iterator>
void AlgoImpl(Iterator first, Iterator last, std::random_access_iterator_tag )
{
// ...
}
template <typename Iterator>
void Algo(Iterator first, Iterator last )
{
using category = typename std::iterator_traits<Iterator>::iterator_category;
AlgoImpl(first, last, category());
}
//
int main(void)
{
// almostEqual1(1,2); // doesn't compile
almostEqual1(1.0,2.0);
almostEqual1(1.0f,2.0f);
// almostEqual1(1.0f,2.0); // doesn't compile
// almostEqual2(1,2); // asserts at compile time
almostEqual2(1.0,2.0);
almostEqual2(1.0f,2.0f);
// almostEqual2(1.0f,2.0); // doesn't compile
// almostEqual3(1,2); // doesn't compile
almostEqual3(1.0,2.0);
almostEqual3(1.0f,2.0f);
// almostEqual3(1.0f,2.0); // doesn't compile
std::list<float> l;
std::vector<float> v;
// Algo(l.begin(),l.end()); // doesn't compile
Algo(v.begin(),v.end());
}
1 comment thread