Ошибка замены

В настоящее время я пытаюсь написать некоторую гибкую математическую библиотеку времени компиляции и только что столкнулся с ошибкой замены, от которой мне не удается избавиться. Вот проблема:

Первым делом пишу рациональный класс, поставлю только ту часть, которая нужна.

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++ выберет первую шаблонную функцию, которая работает с двумя рациональными числами, и с ней все будет в порядке. Однако кажется, что он все еще пытается применить последние две функции и терпит неудачу при попытке сделать это. Что я не могу понять. Некоторая помощь будет приветствоваться :)

EDIT: кажется, что наличие конструктора rational explicit решает проблему, и это здорово. Тем не менее, мне все еще интересно узнать, почему замена так сильно провалилась.


person Morwenn    schedule 05.09.2012    source источник
comment
Может быть, вы пытаетесь добавить целое число в переменную на основе шаблона, и это не может разрешить конфликт? У вас есть оператор +, который принимает smath::rational с одной стороны и int с другой? Как насчет обратного? Поскольку вы используете перегруженные операторы «+», обрабатывайте каждый случай «a+b» как вызов указанных операторов и соответствующим образом проверяйте типы.   -  person Ghost2    schedule 06.09.2012
comment
Мои функции operator+ принимают два рациональных числа или целочисленное рациональное число (в зависимости от того, какой целочисленный тип). Один для интегрального значения справа и один для интегрального значения с левой стороны. Утверждения хорошо работают, когда я даю рациональное число и целочисленное значение. Это просто терпит неудачу с двумя рациональными числами.   -  person Morwenn    schedule 06.09.2012


Ответы (1)


Проблема в типе, переданном в 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 или необходимо сделать это таким образом.

person Dietmar Kühl    schedule 05.09.2012
comment
Спасибо, вы очень помогли! Я еще не знаю всех хитростей шаблона и поэтому не подумал о возможности поставить std::enable_if прямо в объявлении шаблона. Я не могу сказать больше, чем вы можете, является ли это проблемой gcc * или нет. Спасибо еще раз! - person Morwenn; 06.09.2012