В настоящее время я пытаюсь написать какую-то гибкую математическую библиотеку времени компиляции и только что столкнулся с ошибкой замещения, от которой мне не удалось избавиться. Вот проблема:
Прежде всего, я пишу рациональный класс, я поставлю единственную часть, которая нужна.
template<typename T>
class rational
{
static_assert(std::is_integral<T>::value, "Can only contain integral values.");
public:
constexpr rational(T numerator, T denominator);
private:
T _numerator;
T _denominator;
};
И чтобы библиотека была гибкой, я пытался интенсивно использовать SFINAE, чтобы ограничить вызовы функций оператора только рационально-рациональными, рационально-интегральными и интегрально-рациональными, но которые работали бы независимо от интеграла и лежащего в основе тип интеграла есть. Вот объявления функций для operator+
например:
template<typename T, typename U>
constexpr
rational<typename std::common_type<T, U>::type>
operator+(const rational<T>& lhs, const rational<U>& rhs);
template<typename T, typename U>
constexpr
typename std::enable_if<std::is_integral<U>::value, rational<typename std::common_type<T, U>::type>>::type
operator+(const rational<T>& lhs, const U& rhs);
template<typename T, typename U>
constexpr
typename std::enable_if<std::is_integral<T>::value, rational<typename std::common_type<T, U>::type>>::type
operator+(const T& lhs, const rational<U> rhs);
А вот и неисправный сегмент кода. Он не падает из-за static_assert
но предположительно из-за неудачи замещения:
constexpr auto r1 = rational<int>(1, 2);
constexpr auto r2 = rational<int>(2, 4);
static_assert(r1 + r2 == rational<int>(1, 1), "");
Ошибка следующая (я сохранил только ошибки без окружающих блабл):
... required by substitution of 'template<class T, class U> constexpr typename std::enable_if<std::is_integral<T>::value, smath::rational<typename std::common_type<_Tp, _Up>::type> >::type smath::operator+(const T&, smath::rational<U>) [with T = smath::rational<int>; U = int]'
... required from here
... error: operands to ?: have different types 'smath::rational<int>' and 'int'
... required by substitution of 'template<class T, class U> constexpr typename std::enable_if<std::is_integral<U>::value, smath::rational<typename std::common_type<_Tp, _Up>::type> >::type smath::operator+(const smath::rational<T>&, const U&) [with T = int; U = smath::rational<int>]'
... required from here
... error: operands to ?: have different types 'int' and 'smath::rational<int>'
Я предполагал, что g ++ выберет первую шаблонную функцию, которая работает с двумя рациональными числами и будет в порядке. Тем не менее, кажется, что он все еще пытается применить последние две функции и терпит неудачу при попытке сделать это. Это я не могу понять. Некоторая помощь приветствовалась бы 🙂
РЕДАКТИРОВАТЬ: Кажется, что имея rational
конструктор explicit
решает проблему, и это здорово. Тем не менее, мне все еще интересно знать, почему замена не удалась так сложно.
Проблема в том, что тип передан std::enable_if<whatever, T>
, Даже если замена провалится, аргумент должен быть разумным, но это не так. Итак, использование typename std::common_type<T, U>::type
не работает, если нет такого типа для потенциально оцениваемых типов. Вам нужно что-то еще. Что работает, так это создание ошибки замещения, отключающей смешанные целочисленные / рациональные перегрузки в списке аргументов шаблона:
template<typename T, typename U, typename = typename std::enable_if<std::is_integral<U>::value, void>::type>
constexpr
rational<typename std::common_type<T, U>::type>
operator+(const rational<T>& lhs, const U& rhs);
template<typename T, typename U, typename = typename std::enable_if<std::is_integral<T>::value, void>::type>
constexpr
rational<typename std::common_type<T, U>::type>
operator+(const T& lhs, const rational<U> rhs);
Прямо сейчас я не совсем уверен, является ли это обходным решением проблемы gcc или необходимо ли это делать таким образом.
Других решений пока нет …