2012-02-15 2 views
4

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

[В этом описании InRange просто синтаксический сахар для (х> начать & & х < конца) - который по-прежнему хорошо, когда х длинная строка или дорогая функция - но в реальном коде есть дополнительные args для обработки открытого/закрытого характера границ.]

Я был расплывчатым о типах выше. В частности, существуют различные реализации для сравнения по целому и с плавающей запятой, а это означает, что шаблоны не очень уместны, поскольку нет лингвистической группы 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 до конца, и все будет хорошо. Тем не менее, в случаях, когда буквенные двойники записываются sloppily для сравнения с плавающей запятой (что я хочу разрешить), например. 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); 
} 
... 

Не так хорошо - я бы надеялся, что преобразование длинного в двойное было бы автоматическим/неявным - но нормально. Но следующее открытие действительно исказило меня: мой компилятор обнаружил InRange с три Интсом (не лонги) в качестве аргументов, и сказал:

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

с последующим списком всех функции InRange определенной до сих пор! Итак, у C++ нет предпочтений (int, int, int) аргументов arg, которые должны быть разрешены (длинным, длинным, длинным), а не (двойным, двойным, двойным)? Действительно?

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

+0

Для разрешения идентификаторов использование двусмысленности типа, как долго будет: 50л, неподписанных INT: 50u, поплавок: 50.f и т.д. Но, я полагаю, что только решает случай для констант, а не переменных. –

+0

* ... шаблоны не были действительно подходящими, так как нет лингвистической группировки C++, которая отличает int/long/unsigned/size_t и т. Д. От float/double и т. Д. ... * - Хотя это технически верно, на практике это тривиально, чтобы реализовать шаблон для общих случаев и перегрузить ту же функцию для конкретных случаев, точно так же, как Ним указал в ответе ниже. Это означает, что за пределами определенных случаев, которые вы определили для с плавающей запятой, будет использоваться общая версия (которая устранит двусмысленность). –

+0

@ Justin ᚅᚔᚈᚄᚒᚔ: На самом деле это не так. 'std :: numeric_limits' имеет' is_integer' [ссылка] (http://www.cplusplus.com/reference/std/limits/numeric_limits/). –

ответ

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

В теории, мы могли бы быть еще более мягок и просто позволить 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); 
} 

Run в 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

Спасибо Matthieu, это познакомило меня со всей связью вещей, которые Я не знал! Логически, однако, я хочу включить реалистичные сравнения, если * any * из трех аргументов (значение/начало/конец) являются вещественными: существует ли разумный способ выразить это в терминах логических комбинаций в первом шаблоне arg of enable_if? например is std :: enable_if :: значение или is_integral :: значение или is_integral :: value, bool> valid? (где, надеюсь, мои неявно переименованные шаблонные аргументы очевидны) – andybuckley

+0

@andybuckley: да, полностью. Константы booleans можно манипулировать с помощью регулярных логических операторов, чтобы сформировать новую постоянно оцениваемую логическую, без проблем. Однако тогда вам необходимо обеспечить обратное условие для «интегрального» случая, чтобы избежать одновременного включения обоих, что вызовет двусмысленность. Я готовлю это, понаблюдаю за редактированием;) –

0

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

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

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

+0

Это на самом деле реализация, которая у нас была в течение длительного времени, но она соединяется со смешанными типами для x, начала, конца. Возможно, это можно исправить с осторожностью, но после нескольких попыток создания специализаций и т. Д. Вокруг этого я попытался вернуться к необъяснимому пути ... который породил этот вопрос! Спасибо, хотя :) – andybuckley

+0

@andybuckley, если есть смешанные типы, тогда вам нужно сделать каждый тип аргументом шаблона, например, см .: http://ideone.com/3j4xZ – Nim

+0

У меня такое чувство, что мы потом вернемся назад в проблеме двусмысленности при попытке частично специализироваться на удвоения для каждого из аргументов шаблона или для долгого времени для всех из них. Я попробую метод @Matthieu M, так как SFINAE в основном является той проблемой, с которой я пытаюсь справиться здесь ... и если мне это не удастся, я снова рассмотрю этот уже проторенный путь и посмотрю, предложения касаются всех наших прецедентов: компиляция массы кода, которая зависит от этого, была досадно хорошим способом найти ситуации, о которых мы не думали ... – andybuckley

Смежные вопросы