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

时间:2012-02-15 15:13:27

标签: c++ types arguments type-conversion implicit-conversion

我很担心隐式类型转换在C ++参数列表方面的工作原理。特别是,我有一些函数,如inRange(x,start,end),它们返回一个bool,具体取决于x是否在start和end之间。

[在此描述中,范围只是(x> start&& x< end)的语法糖 - 当x是长字符串或昂贵的函数时仍然很好 - 但在实际代码中有额外的args来处理边界的开放/封闭性质。]

我对上面的类型含糊不清。特别是整数和浮点比较有不同的实现,这意味着模板不是很合适,因为没有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)

这不会捕获“很长”或类似,但我们的代码最多只使用双倍和长。所以它看起来很安全:我希望inRange(int,long,long)等会隐式地将int转换为long,一切都会好的。但是,如果为浮点比较(我想要允许)写入双字的双字,则例如, 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);
}
...

不太好 - 我希望将long转换为double会自动/隐含 - 但是没问题。但是下一个发现真的搞砸了我:我的编译器遇到了一个带有三个整数(不是长整数)的inRange作为参数,并说:

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

后面是所有列表到目前为止定义的inRange函数!那么C ++没有(int,int,int)arg列表的首选项(长,长,长)而不是(double,double,double)? 真的?

任何帮助我离开这个洞的帮助都会非常感激......我从来没有想过这么简单的东西,只涉及原始类型可能会变得如此难以解决。使用所有可能的数字类型组合制作全套~1000三个arg函数签名并不是我希望的答案!

2 个答案:

答案 0 :(得分:2)

模板是这里的基础知识,你只需要一些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);
}

理论上,我们甚至可以更多宽松,只允许startend拥有不同的类型。如果我们想。

编辑:更改为只要有一个真实版本,就会切换到真实版本,内置完整性检查。

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

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

答案 1 :(得分:0)

(懒惰的方法:)使用函数模板 - 让编译器担心它..

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

这意味着您可以传入支持operator<的任何对象...并且针对您要执行其他操作的特定类型(例如std::stringconst char*等)重载