Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

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.

Post History

81%
+7 −0
Q&A 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 t...

1 answer  ·  posted 4y ago by dmckee‭  ·  edited 4y ago by Marc.2377‭

#2: Post edited by user avatar Marc.2377‭ · 2020-12-22T00:54:29Z (almost 4 years ago)
copy-edit
  • Pros and cons of vaious type_traits idoms
  • 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 there are for preferring one idiom.
  • I describe the four idioms I've seen as
  • 1. `enable_if` on the return type (functions and methods)
  • 2. Flow control or `static_assert` on type traits. (functions and methods)
  • 3. `enable_if` in `typedef` or `using` statement (can be used in functions and methods, but I think it is more common with classes)
  • 4. 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 developing 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).
  • ```c++
  • #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());
  • }
  • ```
  • 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
  • 1. `enable_if` on the return type (functions and methods)
  • 2. Flow control or `static_assert` on type traits. (functions and methods)
  • 3. `enable_if` in `typedef` or `using` statement (can be used in functions and methods, but I think it is more common with classes)
  • 4. 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).
  • ```c++
  • #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: Initial revision by user avatar dmckee‭ · 2020-11-02T00:02:12Z (about 4 years ago)
Pros and cons of vaious type_traits idoms
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 there are for preferring one idiom.

I describe the four idioms I've seen as

1. `enable_if` on the return type (functions and methods)
2. Flow control or `static_assert` on type traits. (functions and methods)
3. `enable_if` in `typedef` or `using` statement (can be used in functions and methods, but I think it is more common with classes)
4. 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 developing 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).

```c++
#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());
}
```