Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Troubles with std::enable_if and std::is_arithmetic as template parameter

I am trying to implement an OutputArchive template class, which has a templated function processImpl(). That looks like this:

template<typename ArchiveType>
class OutputArchive {
    ...

    template<typename Type, typename std::enable_if_t<std::is_arithmetic_v<Type>>> inline
    ArchiveType& processImpl(Type&& type) {
        // Implementation
    }

    template<typename Type, typename = void> inline
    ArchiveType& processImpl(Type&& type) {
        // Implementation
    }
}

The idea here is that if I pass in a char, int, float, etc to my processImpl() function, the first overload should be used; however, that is not the case. The second overload seems to always be used and I am completely clueless as to what I could be doing wrong. I imagine it does have something to do with the way I am using std::enable_if though

like image 607
Reborn Avatar asked Oct 13 '18 21:10

Reborn


3 Answers

So to make it work you should use std::enable_if for 2 cases. I will show an example for return type, but using template parameter would also work.

template<typename Type> inline
typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
    // Implementation
}

template<typename Type> inline
typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&> processImpl(Type&& type) {
    // Implementation
}

Note the negation in the second case.

But as of C++17 a better way would be to use constexpr:

ArchiveType& processImpl(Type&& type) {
    if constexpr(std::is_arithmetic_v<type>) {
        // implementation
    } else {
        // implementation
    }
}
like image 141
Igor Avatar answered Sep 30 '22 14:09

Igor


There are some problem in your code.

In no particular order

1) not an error (I suppose) but... use typename std::enable_if<...>::type or, starting from C++14, std::enable_if_t<...>; no need to use typename before std::enable_if_t

2) if you want to use std::enable_if in the list of parameter types, this one doesn't work

 template <typename T, std::enable_if_t<(test with T)>>

because, if the test with T is true, become

 template <typename T, void>

that doesn't make sense as signature for a template function.

You can SFINAE enable/disable the return value (see the Igor's or the Marek R's answers) or you can write, instead

 template <typename T, std::enable_if_t<(test with T)> * = nullptr>

that become

 template <typename T, void * = nullptr>

and make sense, as signature, and works

3) as pointed by in comments, you should use std::remove_reference, so

   template <typename Type,
             std::enable_if_t<std::is_arithmetic_v<
                std::remove_reference_t<Type>>> * = nullptr> inline
   ArchiveType & processImpl (Type && type)

now this function should intercept arithmetic values but...

4) the preceding processImpl(), for arithmetic values, collide with the other processImpl() because, in case of arithmetic values, both versions matches and the compiler can't select one over another.

I can suggest two solutions

(a) disable, through SFINAE, the second version in arithmetic cases; I mean, write the second one as follows

   template <typename Type,
             std::enable_if_t<false == std::is_arithmetic_v<
                std::remove_reference_t<Type>>> * = nullptr> inline
    ArchiveType & processImpl (Type && type)

(b) pass through an intermediate function that send an additional int value and receive an int in the arithmetic version and a long in the generic; I mean something as

   template <typename Type,
             std::enable_if_t<std::is_arithmetic_v<
                  std::remove_reference_t<Type>>> * = nullptr> inline
   ArchiveType & processImpl (Type && type, int)
    { /* ... */ }

   template <typename Type>
   ArchiveType & processImpl (Type && type, long)
    { /* ... */ }

   template <typename Type>
   ArchiveType & processImpl (Type && type)
    { return processImpl(type, 0); }

This way the arithmetic version, receiving exactly an int, is preferred (when enabled) over the generic version; the generic version is used otherwise.

The following is a full working C++14 example based over the (b) solution

#include <iostream>
#include <type_traits>

template <typename ArchiveType>
struct OutputArchive
 {
   ArchiveType  value {};

   template <typename Type,
             std::enable_if_t<std::is_arithmetic_v<
                std::remove_reference_t<Type>>> * = nullptr> inline
   ArchiveType & processImpl (Type && type, int)
    {
      std::cout << "--- processImpl aritmetic: " << type << std::endl;

      return value;
    }

   template <typename Type>
   ArchiveType & processImpl (Type && type, long)
    {
      std::cout << "--- processImpl generic: " << type << std::endl;

      return value;
    }

   template <typename Type>
   ArchiveType & processImpl (Type && type)
    { return processImpl(type, 0); }
 };

int main()
 {
   OutputArchive<int>  oa;

   long  l{2l};
   oa.processImpl(l);
   oa.processImpl(3);
   oa.processImpl("abc");
 }
like image 30
max66 Avatar answered Sep 30 '22 15:09

max66


This should do the trick

template<typename ArchiveType>
class OutputArchive {
    ...
    template<typename Type>
    inline
    typename std::enable_if_t<std::is_arithmetic_v<Type>, ArchiveType&>
    processImpl(Type type) {
        // Implementation
    }

    template<typename Type>
    inline
    typename std::enable_if_t<!std::is_arithmetic_v<Type>, ArchiveType&>
    processImpl(Type&& type) {
        // Implementation
    }
};

Live sample.

like image 27
Marek R Avatar answered Sep 30 '22 16:09

Marek R