如何使用 SFINAE 来防止模板函数变窄?
How can I use SFINAE to prevent narrowing in a template function?
我正在尝试为 Vector class 实现基本算术运算,并希望支持混合基础类型,同时防止发生缩小。
template <typename T1,typename T2>
Vector<T1> operator+( Vector<T1> lhs, const Vector<T2>& rhs, std::enable_if< ! is_narrowing_conversion<T2,T1>::value >::type* = nullptr )
{ return lhs += rhs; }
我想实现 is_narrowing_conversion 以便仅在类型不缩小时才允许转换。以下是一些示例:
Vector<double> a = Vector<double>() + Vector<float>(); //OK
Vector<float> a = Vector<float> + Vector<double>; //Fails to find the operator+ function
最后我想编写第二个模板 operator+ 函数,它将通过返回一个 Vector 来处理相反的情况。
我找到了这个 post with an incomplete example。但这还不够,因为他指出它不允许 uint8_t 到 uint64_t 转换。
我也发现了Daniel Krügler's paper on fixing is_constructible。特别是在这篇论文中,他提到使用列表初始化,它已经具有缩小的语义,但我不确定如何将他提到的内容转换成我可以用于 SFINAE 模板推导的适当特征。
通过 std::common_type
使用 SFINAE 怎么样?
下面是一个简化的例子(没有Vector
但是简单的值;没有operator+()
但是sum()
函数)但是我希望你能理解我的意思
#include <iostream>
#include <type_traits>
template <typename T1, typename T2>
T1 sum (T1 t1,
T2 const & t2,
typename std::enable_if<std::is_same<
T1,
typename std::common_type<T1, T2>::type
>::value>::type * = nullptr)
{ return t1 += t2; }
int main()
{
float a = 1.1f;
double b = 2.2;
long double c = 3.3;
std::cout << sum(b, a) << std::endl;
std::cout << sum(c, a) << std::endl;
std::cout << sum(c, b) << std::endl;
// std::cout << sum(a, b) << std::endl; compilation error
// std::cout << sum(a, c) << std::endl; compilation error
// std::cout << sum(b, c) << std::endl; compilation error
}
有一些关于 Vector
的假设,但您应该明白:
template <typename T1,typename T2>
Vector<typename std::common_type<T1, T2>::type>
operator+(Vector<T1> const& lhs, Vector<T2> const& rhs)
{
std::size_t const n = std::min(lhs.size(), rhs.size());
Vector<typename std::common_type<T1, T2>::type> res(n);
for(std::size_t i{}; i < n; ++i) res[i] = a[i] + b[i];
return res;
}
Vector<double> a = Vector<double>() + Vector<float>(); // OK
Vector<double> b = Vector<float>() + Vector<double>(); // OK
您可以使用 {}
构造函数并让它为您检测 narrowing conversions。
你可以用这样的东西来做到这一点:
template<typename T>
struct Vector {
T value;
};
template <typename T1, typename T2>
auto operator+(Vector<T1>, const Vector<T2>& rhs)
-> decltype(T1{rhs.value}, Vector<T1>{})
{ return {}; }
int main() {
auto a = Vector<double>{} + Vector<float>{};
//auto b = Vector<float>{} + Vector<double>{};
(void)a;
}
也就是说,对于涉及尾随 return 类型的 sfinae 表达式,您可以使用通常的形式。
如果您的 Vector
class 模板没有默认构造函数,您仍然可以使用 std::declval
:
来解决它
-> decltype(T1{rhs.value}, std::declval<Vector<T1>>())
请注意,由于 [dcl.init.list]/7.2 和后面的其他项目符号,您不能简单地在上面的代码中执行此操作:
-> decltype(T1{T2{}}, Vector<T1>{})
否则,在您的特定示例中,以下内容将有效:
auto b = Vector<float>{} + Vector<double>{};
因此,您必须使用与 rhs
一起提供的实际值,并且(让我说)使用用于特化 lhs
的实际类型对其进行测试 .
只要包含的值是可访问的(它是有效的或者操作员是您的 class 的朋友)这应该不是问题。
附带说明一下,您无法通过仅使用类型 T1
和 T2
.[=47= 来正确定义在您的情况下正常工作的 is_narrowing_conversion
]
例如,考虑 [dcl.init.list]/7.2(强调我的)和您提出的测试代码:
[...] or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly) [...]
因为您没有可用来进行测试的实际值,您所能做的就是尝试类似 T1{T2{}}
的方法。无论如何,这是行不通的。换句话说,由于上面提到的项目符号,float{double{}}
将被无误地接受。
类似的情况适用于少数其他类型。
另一个有效的方法是使用 std::common_type
.
反正已经有提出来了。不值得再次重复示例代码。
我正在尝试为 Vector class 实现基本算术运算,并希望支持混合基础类型,同时防止发生缩小。
template <typename T1,typename T2>
Vector<T1> operator+( Vector<T1> lhs, const Vector<T2>& rhs, std::enable_if< ! is_narrowing_conversion<T2,T1>::value >::type* = nullptr )
{ return lhs += rhs; }
我想实现 is_narrowing_conversion 以便仅在类型不缩小时才允许转换。以下是一些示例:
Vector<double> a = Vector<double>() + Vector<float>(); //OK
Vector<float> a = Vector<float> + Vector<double>; //Fails to find the operator+ function
最后我想编写第二个模板 operator+ 函数,它将通过返回一个 Vector 来处理相反的情况。
我找到了这个 post with an incomplete example。但这还不够,因为他指出它不允许 uint8_t 到 uint64_t 转换。
我也发现了Daniel Krügler's paper on fixing is_constructible。特别是在这篇论文中,他提到使用列表初始化,它已经具有缩小的语义,但我不确定如何将他提到的内容转换成我可以用于 SFINAE 模板推导的适当特征。
通过 std::common_type
使用 SFINAE 怎么样?
下面是一个简化的例子(没有Vector
但是简单的值;没有operator+()
但是sum()
函数)但是我希望你能理解我的意思
#include <iostream>
#include <type_traits>
template <typename T1, typename T2>
T1 sum (T1 t1,
T2 const & t2,
typename std::enable_if<std::is_same<
T1,
typename std::common_type<T1, T2>::type
>::value>::type * = nullptr)
{ return t1 += t2; }
int main()
{
float a = 1.1f;
double b = 2.2;
long double c = 3.3;
std::cout << sum(b, a) << std::endl;
std::cout << sum(c, a) << std::endl;
std::cout << sum(c, b) << std::endl;
// std::cout << sum(a, b) << std::endl; compilation error
// std::cout << sum(a, c) << std::endl; compilation error
// std::cout << sum(b, c) << std::endl; compilation error
}
有一些关于 Vector
的假设,但您应该明白:
template <typename T1,typename T2>
Vector<typename std::common_type<T1, T2>::type>
operator+(Vector<T1> const& lhs, Vector<T2> const& rhs)
{
std::size_t const n = std::min(lhs.size(), rhs.size());
Vector<typename std::common_type<T1, T2>::type> res(n);
for(std::size_t i{}; i < n; ++i) res[i] = a[i] + b[i];
return res;
}
Vector<double> a = Vector<double>() + Vector<float>(); // OK
Vector<double> b = Vector<float>() + Vector<double>(); // OK
您可以使用 {}
构造函数并让它为您检测 narrowing conversions。
你可以用这样的东西来做到这一点:
template<typename T>
struct Vector {
T value;
};
template <typename T1, typename T2>
auto operator+(Vector<T1>, const Vector<T2>& rhs)
-> decltype(T1{rhs.value}, Vector<T1>{})
{ return {}; }
int main() {
auto a = Vector<double>{} + Vector<float>{};
//auto b = Vector<float>{} + Vector<double>{};
(void)a;
}
也就是说,对于涉及尾随 return 类型的 sfinae 表达式,您可以使用通常的形式。
如果您的 Vector
class 模板没有默认构造函数,您仍然可以使用 std::declval
:
-> decltype(T1{rhs.value}, std::declval<Vector<T1>>())
请注意,由于 [dcl.init.list]/7.2 和后面的其他项目符号,您不能简单地在上面的代码中执行此操作:
-> decltype(T1{T2{}}, Vector<T1>{})
否则,在您的特定示例中,以下内容将有效:
auto b = Vector<float>{} + Vector<double>{};
因此,您必须使用与 rhs
一起提供的实际值,并且(让我说)使用用于特化 lhs
的实际类型对其进行测试 .
只要包含的值是可访问的(它是有效的或者操作员是您的 class 的朋友)这应该不是问题。
附带说明一下,您无法通过仅使用类型 T1
和 T2
.[=47= 来正确定义在您的情况下正常工作的 is_narrowing_conversion
]
例如,考虑 [dcl.init.list]/7.2(强调我的)和您提出的测试代码:
[...] or from double to float, except where the source is a constant expression and the actual value after conversion is within the range of values that can be represented (even if it cannot be represented exactly) [...]
因为您没有可用来进行测试的实际值,您所能做的就是尝试类似 T1{T2{}}
的方法。无论如何,这是行不通的。换句话说,由于上面提到的项目符号,float{double{}}
将被无误地接受。
类似的情况适用于少数其他类型。
另一个有效的方法是使用 std::common_type
.
反正已经有