C ++ неявное преобразование типов в списках аргументов - PullRequest
4 голосов
/ 15 февраля 2012

Меня смущает, как неявное преобразование типов работает в отношении списков аргументов C ++. В частности, у меня есть несколько функций, называемых inRange (x, start, end), которые возвращают bool в зависимости от того, находится ли x между началом и концом.

[В этом описании inRange является просто синтаксическим сахаром для (x> start && x

Я был расплывчатым в отношении типов выше В частности, существуют различные реализации для сравнения целых чисел и чисел с плавающей запятой, и это означало, что шаблоны не были действительно уместны, поскольку нет лингвистической группировки C ++, которая отличает int / long / unsigned / size_t и т. Д. От float / double и т. Д. Поэтому я попытался использовать систему типов, определив две версии inRange с достаточно широкими типами int / float:

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 с тремя целыми числами (не longs) в качестве аргументов и сказал:

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

, за которым следует список всех функций inRange, определенных до сих пор! Таким образом, C ++ не имеет предпочтения для (int, int, int) списков аргументов быть разрешенными (long, long, long), а не (double, double, double)? Действительно

Любая помощь, чтобы вытащить меня из этой дыры, была бы очень признательна ... Я бы никогда не подумал, что что-то настолько простое, с участием только примитивных типов, может оказаться настолько трудным для разрешения. Я не надеюсь, что полный набор из ~ 1000 трех-аргументных сигнатур со всеми возможными комбинациями числовых типов!

Ответы [ 2 ]

2 голосов
/ 15 февраля 2012

Шаблоны являются основой здесь, вам просто нужно немного 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);
}

Теоретически, мы можем быть даже больше снисходительными и просто позволить start и endесть разные типы.Если мы хотим.

EDIT : Изменено переключение на реальную версию, как только есть одна реальная, со встроенной проверкой работоспособности.

#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
0 голосов
/ 15 февраля 2012

(Ленивый подход :) используйте шаблон функции - и позвольте компилятору беспокоиться об этом ..

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

Это означает, что вы можете передать любой объект, который поддерживает operator< ... И перегрузка дляконкретные типы, где вы хотите сделать что-то другое (скажем, std::string, const char* и т. д.)

...