简体   繁体   English

参数列表中的C ++隐式类型转换

[英]C++ implicit type conversion in argument lists

I am confused about how implicit type conversion works with regard to C++ argument lists. 我对C ++参数列表的隐式类型转换如何工作感到困惑。 In particular, I have a bunch of functions called like inRange(x, start, end), which return a bool depending on whether x is between start and end. 特别是,我有一堆称为inRange(x,start,end)的函数,它们根据x是否在start和end之间返回布尔值。

[In this description inRange is just syntactic sugar for (x > start && x < end) -- which is still nice when x is a long string or an expensive function -- but in the real code there are extra args for handling the open/closed nature of the boundaries.] [在此描述中,inRange只是(x> start && x <end)的语法糖-当x是一个长字符串或一个昂贵的函数时,它仍然很不错-但在实际代码中,还有一些用于处理open的args。 /边界的封闭性质。]

I was vague about the types above. 我对上面的类型含糊不清。 In particular there are different implementations for integer and floating point comparisons, and this meant that templates were not really appropriate since there is no C++ linguistic grouping that differentiates int/long/unsigned/size_t etc. from float/double, etc. So I tried to use the type system by defining two versions of inRange with wide enough int/float types: 特别是整数和浮点比较有不同的实现方式,这意味着模板并不是真正合适的,因为没有C ++语言分组将int / long / unsigned / size_t等与float / double等区分开。所以我尝试了通过定义两个具有足够int / float类型的inRange版本来使用类型系统:

inline bool inRange(long x, long start, long end)
inline bool inRange(double x, double start, double end)

This won't catch "long long" or similar, but our code only uses at most doubles and longs. 这不会捕获“ long long”或类似的东西,但是我们的代码最多只使用double和longs。 So it looked pretty safe: my hope was that inRange(int, long, long) etc. would implicitly upcast the int to a long and everything would be fine. 因此它看起来很安全:我希望inRange(int,long,long)等可以隐式地将int转换为long,一切都会好起来的。 However, in cases where literal doubles are written sloppily for the floating point comparison (which I want to allow), eg inRange(mydouble, 10, 20), I also had to add a bunch of explicit casts to get rid of compiler warnings and ensure that the floating point comparison is used: 但是,在为浮点比较(我希望允许)草率地写入文字双精度数的情况下,例如inRange(mydouble,10,20),我还必须添加一堆显式强制转换以摆脱编译器警告和确保使用浮点比较:

inline bool inRange(double value, long low, long high) {
  return inRange(value, (double)low, (double)high);
}
inline bool inRange(double value, double low, long high) {
  return inRange(value, low, (double)high, lowbound, highbound);
}
...

Not so nice -- I'd hoped that the conversion of long to double would have been automatic/implicit -- but ok. 不太好-我希望从long到double的转换将是自动/隐式的-但是可以。 But the next discovery really screwed me: my compiler encountered an inRange with three ints (not longs) as arguments, and said: 但是,下一个发现确实让我感到困惑:我的编译器遇到了一个inRange,带有三个int(不是long)作为参数,并说:

call of overloaded ‘inRange(int&, int&, int&)’ is ambiguous

followed by a list of all the inRange functions defined so far! 然后是到目前为止定义的所有 inRange函数的列表! So C++ doesn't have a preference for (int, int, int) arg lists to be resolved by (long, long, long) rather than by (double, double, double)? 因此,C ++没有优先选择使用(long,long,long)而不是(double,double,double)来解析(int,int,int)arg列表吗? Really? 真?

Any help to get me out of this hole would be much appreciated... I'd never have thought something so simple with only primitive types involved could turn out to be so hard to resolve. 任何使我摆脱困境的帮助都将不胜感激……我从来没有想到过,仅涉及原始类型的简单事情就变得如此难以解决。 Making the full set of ~1000 three-arg function signatures with all possible numerical type combinations is not the answer I'm hoping for! 用所有可能的数值类型组合制作全套〜1000个三参数函数签名不是我想要的答案!

Templates are the basics here, you just need some SFINAE. 模板是这里的基础,您只需要一些SFINAE。

#include <limits>
#include <utility>

template <typename T>
struct is_integral {
  static bool const value = std::numeric_limits<T>::is_integer;
};

template <typename Integral, typename T>
typename std::enable_if<is_integral<Integral>::value, bool>::type
inRange(Integral x, T start, T end) {
  return x >= static_cast<Integral>(start) and x <= static_cast<Integral>(end);
}

template <typename Real, typename T>
typename std::enable_if<not is_integral<Real>::value, bool>::type
inRange(Real x, T start, T end) {
  return x >= static_cast<Real>(start) and x <= static_cast<Real>(end);
}

In theory, we could be even more lenient and just allow start and end to have different types. 从理论上讲,我们可以更加宽容,只允许startend具有不同的类型。 If we want to. 如果我们想。

EDIT : Changed to switch to the real version as soon as there are one real, with built-in sanity check. 编辑 :更改为一旦有真实版本,便立即切换到真实版本,并具有内置的健全性检查功能。

#include <limits>
#include <utility>
#include <iostream>

template <typename T>
struct is_integral {
  static bool const value = std::numeric_limits<T>::is_integer;
};

template <typename T>
struct is_real {
  static bool const value = not is_integral<T>::value;
};

template <typename T, typename L, typename R>
struct are_all_integral {
  static bool const value = is_integral<T>::value and
                            is_integral<L>::value and
                            is_integral<R>::value;
};

template <typename T, typename L, typename R>
struct is_any_real {
  static bool const value = is_real<T>::value or
                            is_real<L>::value or
                            is_real<R>::value;
};


template <typename T, typename L, typename R>
typename std::enable_if<are_all_integral<T, L, R>::value, bool>::type
inRange(T x, L start, R end) {
  typedef typename std::common_type<T, L, R>::type common;
  std::cout << "  inRange(" << x << ", " << start << ", " << end << ") -> Integral\n";
  return static_cast<common>(x) >= static_cast<common>(start) and
         static_cast<common>(x) <= static_cast<common>(end);
}

template <typename T, typename L, typename R>
typename std::enable_if<is_any_real<T, L, R>::value, bool>::type
inRange(T x, L start, R end) {
  typedef typename std::common_type<T, L, R>::type common;
  std::cout << "  inRange(" << x << ", " << start << ", " << end << ") -> Real\n";
  return static_cast<common>(x) >= static_cast<common>(start) and
         static_cast<common>(x) <= static_cast<common>(end);
}

int main() {
  std::cout << "Pure cases\n";
  inRange(1, 2, 3);
  inRange(1.5, 2.5, 3.5);

  std::cout << "Mixed int/unsigned\n";
  inRange(1u, 2, 3);
  inRange(1, 2u, 3);
  inRange(1, 2, 3u);

  std::cout << "Mixed float/double\n";
  inRange(1.5f, 2.5, 3.5);
  inRange(1.5, 2.5f, 3.5);
  inRange(1.5, 2.5, 3.5f);

  std::cout << "Mixed int/double\n";
  inRange(1.5, 2, 3);
  inRange(1, 2.5, 3);
  inRange(1, 2, 3.5);

  std::cout << "Mixed int/double, with more doubles\n";
  inRange(1.5, 2.5, 3);
  inRange(1.5, 2, 3.5);
  inRange(1, 2.5, 3.5);
}

Run at ideone : ideone上运行:

Pure cases
  inRange(1, 2, 3) -> Integral
  inRange(1.5, 2.5, 3.5) -> Real
Mixed int/unsigned
  inRange(1, 2, 3) -> Integral
  inRange(1, 2, 3) -> Integral
  inRange(1, 2, 3) -> Integral
Mixed float/double
  inRange(1.5, 2.5, 3.5) -> Real
  inRange(1.5, 2.5, 3.5) -> Real
  inRange(1.5, 2.5, 3.5) -> Real
Mixed int/double
  inRange(1.5, 2, 3) -> Real
  inRange(1, 2.5, 3) -> Real
  inRange(1, 2, 3.5) -> Real
Mixed int/double, with more doubles
  inRange(1.5, 2.5, 3) -> Real
  inRange(1.5, 2, 3.5) -> Real
  inRange(1, 2.5, 3.5) -> Real

(Lazy approach:) use a function template - and let the compiler worry about it.. (惰性方法:)使用函数模板-让编译器担心它。

template <typename T1>
inline bool inRange(T1 x, T1 start, T1 end)
{
  // do stuff
}

This means you can pass in any object that supports operator< ... And overload for the specific types where you want to do something different (say std::string , const char* etc.) 这意味着您可以传入任何支持operator< ...的对象,并在想要执行某些operator<的特定类型上重载(例如std::stringconst char*等)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM