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
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
}
}
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");
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With