如何使用SFINAE防止模板功能缩小?

时间:2017-06-07 20:31:07

标签: c++ c++11 sfinae narrowing

我正在尝试为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

最后,我想编写第二个模板运算符+函数,通过返回Vector来处理逆情况。

我发现了这篇文章with an incomplete example。但这是不够的,因为他指出它不允许uint8_t进行uint64_t转换。

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

3 个答案:

答案 0 :(得分:0)

如何通过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
 }

答案 1 :(得分: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

答案 2 :(得分:0)

您可以使用{}构造函数,让它为您检测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;
}

也就是说,您可以使用通常的表格来处理涉及尾随返回类型的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提供的实际值和(让我说)测试以及用于专门化lhs的实际类型。
只要可以访问所包含的值(无论它是有效的还是运算符是您班级的朋友),这应该不是问题。

作为旁注,您无法仅使用is_narrowing_conversionT1类型正确定义在您的案例中正常运作的T2
例如,考虑[dcl.init.list]/7.2(强调我的)和你提出的测试代码:

  

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

因为您没有用于进行测试的实际值,所以您可以尝试使用T1{T2{}}之类的东西。无论如何这不起作用。换句话说,由于上面提到的子弹,float{double{}}将被接受而没有错误 类似的东西适用于其他几种类型。

另一种有效的方法是使用std::common_type 无论如何,已经提出了pretty good answer。不值得重复示例代码。