2

我正在尝试为 Vector 类实现基本算术运算,并希望支持混合底层类型,同时防止发生缩小。

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 来处理相反的情况。

我发现这篇文章有一个不完整的例子。但这还不够,因为他指出它不允许 uint8_t 到 uint64_t 转换。

我还发现了Daniel Krügler 关于修复 is_constructible 的论文。特别是在本文中,他提到使用列表初始化,它已经具有缩小语义,但我不确定如何将他提到的内容转换为可用于 SFINAE 模板推导的适当特征。

4

3 回答 3

0

如何通过 SFINAE 使用std::common_type

以下是一个简化的示例(没有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
 }
于 2017-06-07T21:01:26.853 回答
0

有一些关于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
于 2017-06-07T21:29:24.847 回答
0

您可以使用{}构造函数并让它为您检测缩小转换
你可以这样做:

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;
}

也就是说,您可以对涉及尾随返回类型的 sfinae 表达式使用通常的形式。
如果您的Vector类模板没有默认构造函数,您仍然可以使用以下方法解决它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并且(让我说)使用用于 specialize 的实际类型对其进行测试lhs
只要包含的值是可访问的(它是有效的或者操作员是您班级的朋友),这应该不是问题。


作为旁注,您无法is_narrowing_conversion通过仅使用类型T1和来正确定义在您的情况下正常工作的 a T2
例如,考虑[dcl.init.list]/7.2(重点是我的)和您提出的测试代码:

[...] 或从 double 到 float,除非源是常量表达式并且转换后的实际值在可以表示的值范围内(即使不能精确表示) [...]

因为您没有用于进行测试的实际值,您所能做的就是尝试类似T1{T2{}}. 无论如何,这是行不通的。换句话说,float{double{}}由于上面提到的项目符号,将被正确接受。
类似的东西适用于少数其他类型。


另一种有效的方法是使用std::common_type.
无论如何,已经有一个很好的答案提出了它。不值得再次重复示例代码。

于 2017-06-07T22:03:20.373 回答