I have some API that serializes values. I have created a wrapper that helps by having a simple one method that should accept all supported types.
Most types require explicit implementation, but all integer types are accepted by single method on that API. I wrote this code:
// Used to allow for human readable error on missing overload
template<typename T>
struct fail_if_invoked : std::false_type
{ };
// This overload should get selected if value is not integer type and explicit overload does not exist
template<typename TVal, typename std::enable_if<!std::is_integral<TVal>::value>::type* = nullptr>
void accept_value(TVal && vadd)
{
static_assert(fail_if_invoked<TVal>::value, "Missing implementation for this type");
}
// This overload should handle all integer types
template<typename TInteger, typename std::enable_if<std::is_integral<TInteger>::value>::type* = nullptr>
void accept_value(TInteger num)
{
...
}
void accept_value(const char*)
{
...
}
However, when unsigned int is the argument to accept_value, I get an error about ambiguous call, for these overloads:
my_helper.h:37:10: note: candidate function [with TVal = unsigned short &, $1 = nullptr]
void accept_value(TVal && vadd)
^
my_helper.h:44:10: note: candidate function [with TInteger = unsigned short, $1 = nullptr]
void accept_value(TInteger num)
^
my_helper.h:59:10: note: candidate function
void accept_value(double num)
^
my_helper.h:79:10: note: candidate function
void accept_value(bool num)
I sort of understand the bool and double, although it wasn’t an issue when I had explicit overloads for everything. But why is the void add_value(TVal && vadd) still in the list when !std::is_integral<TVal> should disable it?
And is it even possible to have generic implementation for all integral types while also having bool and double overloads?
>Solution :
You need to wrap the template parameter in std::decay_t to get the raw type without any reference or const, because std::integral<int&> will evaluate to false.
Update – addition explanation
The signature accept_value(TVal && vadd) accepts the parameter using an rvalue-reference (often called a universal reference). If you pass a variable of type int, TVal will deduce to int&. If you pass a literal integer like 1, TVal will deduce to int. Without the std::decay_t, this will cause different results when std::is_integral is evaluated. Overload resolution will behave accordingly.
End Update
Sample Code
#include <iostream>
// Used to allow for human readable error on missing overload
template<typename T>
struct fail_if_invoked : std::false_type
{ };
// This overload should get selected if value is not integer type and explicit overload does not ex\
ist
template<typename TVal,
typename std::enable_if<!std::is_integral<std::decay_t<TVal>>::value>::type* = nullptr>
void accept_value(TVal && vadd)
{
static_assert(fail_if_invoked<TVal>::value, "Missing implementation for this type");
}
// This overload should handle all integer types
template<typename TInteger,
typename std::enable_if<std::is_integral<std::decay_t<TInteger>>::value>::type* = nullptr>
void accept_value(TInteger num)
{
}
int main(int argc, const char *argv[]) {
int a{};
accept_value(a);
accept_value(1);
accept_value("char");
return 0;
}
Output
/work/so/scratch/src/p1.cpp:16:5: error: static assertion
failed due to requirement 'fail_if_invoked<const char (&)[5]>::value': Missing
implementation for this type
static_assert(fail_if_invoked<TVal>::value, "Missing implementation for this type");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/work/so/scratch/src/p1.cpp:30:5: note: in instantiation of
function template specialization 'accept_value<const char (&)[5], nullptr>' requested here
accept_value("char");
^
1 error generated.