2017-02-10 12 views
1

Написал арифметическую оболочку, которая могла бы помочь обнаружить ошибки overflow/underflow, однако застряла с довольно коварной проблемой в процессе.Изменение типа возвращаемого шаблона, похоже, влияет на разрешение перегрузки.

Предположим, что у нас есть класс, который обрабатывает все, что может вызвать переполнение через некоторые перегруженные операторы и неявно приписывается базовому типу для всего остального. Этот пример содержит только бинарный оператор плюс:

template<typename T_> 
class Wrapper 
{ 
    public: 
    Wrapper(T_ val_) : m_value(val_) { } // converting constructor 
    operator T_(void) const { return m_value; } // underlying type conversion 
    // some other methods 

    // binary plus operators: 
    template<typename U_> 
    const Wrapper<decltype(T_() + U_())> operator +(U_ val_) const 
    { 
     // supposed to handle 'Wrapped + Unwrapped' case 
     return m_value + val_; 
    } 
    template<typename U_> 
    const Wrapper<decltype(T_() + U_())> operator +(Wrapper<U_> other_) const 
    { 
     // supposed to handle 'Wrapped + Wrapped' case 
     return m_value + other_.m_value; 
    } 

    template<typename U0_, typename U1_> 
    friend const Wrapper<decltype(U0_() + U1_())> operator +(U0_ val_, Wrapper<U1_> wrapper_) 
    { 
     // supposed to handle 'Unwrapped + Wrapped' case 
     return val_ + wrapper_.m_value; 
    } 

    private: 
    T_ m_value; 
}; 

Это (если я не пропустить что-то в то время как приклеивая его здесь) отлично компилируется и работает , как ожидается, в подобных ситуациях (каждый возможный один из них, в основном) :

Wrapper<int> val = 3.14f; 
::std::cout << val + 42 << ::std::endl; // Wrapped + Unwrapped 
::std::cout << 42 + val << ::std::endl; // Unwrapped + Wrapped 
::std::cout << val + val << ::std::endl; // Wrapped + Wrapped 

Однако всякий раз, когда я пытаюсь сделать псевдоним для decltype(...) части либо «Облаченный + развернутого» или «развернутого + обернутый», например, как это:

template<typename T0_, typename T1_> 
struct Result 
{ 
    typedef decltype(T0_() + T1_()) Type; 
}; 

template<typename T_> 
class Wrapper 
{ 
    //... 
    template<typename U_> 
    const Wrapper<typename Result<T_, U_>::Type> operator +(U_ val_) const 
    //... 
    template<typename U0_, typename U1_> 
    friend const Wrapper<typename Result<U0_, U1_>::Type> operator +(U0_ val_, Wrapper<U1_> wrapper_) 
    //... 
}; 

Пример «Обернутый + обернутый» не хочет компилироваться, потому что разрешение перегрузки меняется на нежелательный вариант. Он выдает ошибку о недоступности конструктора по умолчанию для Wrapper<int>, называя попытку использовать либо «Обернутый + Unwrapped», либо «Unwrapped + Wrapped», оба из которых не подходят для правильной обработки рассматриваемого случая.

И это меня смущает, поскольку изменение типа возврата приводит к изменению поведения разрешения перегрузки. По достоинству оценят любые советы по этому вопросу.

+1

Нежелательные Перегрузки были SFINAE'd прочь; теперь это не так, потому что потенциально недопустимое выражение было перенесено в определение «Результат» и вне контекста. –

+0

Итак, я был не в порядке, чтобы предположить, что вариант «Обернутый + обернутый» будет считаться более подходящим для разрешения перегрузки в такой ситуации. Возьмите за ответ. – so100217

+1

Это может быть лучшая перегрузка (из-за частичного заказа и т. Д.), Но вы даже не добираетесь туда: вы вызываете жесткую ошибку, пытаясь сформировать набор кандидатов. –

ответ

1

Вот примерно как работает разрешение перегрузки:

  1. Имя поиска, чтобы найти функции кандидатов и шаблоны функций.
  2. Выделите аргументы шаблона для каждого шаблона функции и замените выводимые аргументы в шаблон, чтобы создать специализированную специализацию шаблона в качестве кандидата. Выбросьте любой шаблон функции, для которого вычет не выполняется (включая сбой замены).
  3. Сравните кандидатов. Выберите лучший или пожаловаться, если его нет.

Если шаг 2 запускает жесткую ошибку - путем формирования недопустимой конструкции вне непосредственного контекста сигнатуры шаблона функции - тогда ваша программа плохо сформирована; вы никогда не дойдете до шага 3. Неважно, был ли кандидат не выбран на шаге 3, если ошибки там не было.

Здесь decltype(T_() + U_()) был в непосредственном контексте первоначально. Поэтому, когда U_ был выведен на Wrapper<...> и заменен на подпись, это выражение плохо сформировано, но ошибка находится в непосредственном контексте, и поэтому это сбой замены. Но когда вы переносите это выражение в отдельный шаблон класса Result, ошибка больше не находится в непосредственном контексте подписи шаблона функции, поэтому вместо этого это сложная ошибка.

Если вы не хотите повторять выражение несколько раз, используйте шаблон псевдоним:

template<class T, class U> 
using result = decltype(T() + U()); 
+0

Еще раз спасибо за этот, несомненно, полезный ответ. Он исчерпывающе объясняет проблему, которую я испытываю. Очень жаль, что вы не можете узнать все особенности шаблона, так как мне абсолютно необходимо закончить то, над чем я работаю как можно скорее. P.S: Я знаю о шаблоне сглаживания, srtuct существует для некоторых дополнительных статических методов. – so100217