英文:
std::enable_if for std::is_integral and its negation both show as ambiguous candidate overloads
问题
Here's the translated code:
// 用于在缺少重载时提供易读的错误
template<typename T>
struct fail_if_invoked : std::false_type
{ };
// 如果值不是整数类型且没有显式重载,则应选择此重载
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");
}
// 此重载应处理所有整数类型
template<typename TInteger, typename std::enable_if<std::is_integral<TInteger>::value>::type* = nullptr>
void accept_value(TInteger num)
{
...
}
void accept_value(const char*)
{
...
}
Regarding your questions:
-
The
void accept_value(TVal && vadd)
function is still in the list because SFINAE (Substitution Failure Is Not An Error) doesn't completely eliminate it. It's just that the compiler marks it as a candidate with a lower priority due to thestd::enable_if
. In your specific case withunsigned int
, both the integral overload and the generic one are viable candidates, leading to an ambiguous call. -
It is possible to have a generic implementation for all integral types while also having
bool
anddouble
overloads, but you need to refine your SFINAE conditions to ensure that there's no overlap between the overloads. In this case, the overlap between the generic overload and the integral overload is causing the ambiguity. You might need to use more specific type traits to differentiate them.
英文:
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?
答案1
得分: 3
你需要将模板参数用 std::decay_t
包裹起来,以获得没有引用或常量修饰的原始类型,因为 std::integral<int&>
会被判断为假。
更新 - 额外说明
签名 accept_value(TVal && vadd)
使用右值引用(通常称为通用引用)接受参数。如果你传递类型为 int
的变量,TVal
将被推导为 int&
。如果你传递字面整数如 1
,TVal
将被推导为 int
。如果没有使用 std::decay_t
,在评估 std::is_integral
时会导致不同的结果。重载决议会相应地行为。
更新结束
示例代码
#include <iostream>
// 用于在缺少重载时提供人类可读的错误
template<typename T>
struct fail_if_invoked : std::false_type
{ };
// 如果值不是整数类型且不存在显式重载,将选择这个重载
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");
}
// 这个重载应该处理所有整数类型
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;
}
输出
/work/so/scratch/src/p1.cpp:16:5: 错误: 静态断言失败,要求 'fail_if_invoked<const char (&)[5]>::value': 缺少此类型的实现
static_assert(fail_if_invoked<TVal>::value, "Missing implementation for this type");
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/work/so/scratch/src/p1.cpp:30:5: 注意: 在此处请求了函数模板 specialization 'accept_value<const char (&)[5], nullptr>'
accept_value("char");
^
1 error generated.
更新
顺便说一下,在 c++20
中会更容易一些
template<class T> requires (not std::is_integral_v<std::decay_t<T>>)
void accept_value(T&& vadd)
{
static_assert(std::is_integral_v<std::decay_t<T>>, "Missing implementation for this type");
}
template<class T> requires (std::is_integral_v<std::decay_t<T>>)
void accept_value(T&& num)
{
}
英文:
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 - additional 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.
Update
As an aside, this gets much easier with c++20
template<class T> requires (not std::is_integral_v<std::decay_t<T>>)
void accept_value(T&& vadd)
{
static_assert(std::is_integral_v<std::decay_t<T>>, "Missing implementation for this type");
}
template<class T> requires (std::is_integral_v<std::decay_t<T>>)
void accept_value(T&& num)
{
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论