Неявное преобразование при перегрузке операторов для шаблонных классов - PullRequest
20 голосов
/ 17 января 2012

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

class foo
{
public:

    foo() = default;

    foo(int that)
    {}

    foo& operator +=(foo rhs)
    {
        return *this;
    }
};

foo operator +(foo lhs, foo rhs)
{
    lhs += rhs;
    return lhs;
}

Как и ожидалось, корректно компилируются следующие строки:

foo f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // OK
f = 5 + f; // OK

С другой стороны, когда класс foo объявлен как простой шаблон, подобный этому:

template< typename T >
class foo
{
public:

    foo() = default;

    foo(int that)
    {}

    foo& operator +=(foo rhs)
    {
        return *this;
    }
};

template< typename T >
foo< T > operator +(foo< T > lhs, foo< T > rhs)
{
    lhs += rhs;
    return lhs;
}

Следующие строки компилируются с ошибками:

foo< int > f, g;
f = f + g; // OK
f += 5; // OK
f = f + 5; // Error (no match for operator+)
f = 5 + f; // Error (no match for operator+)

Я хотел бы понять, почему компилятор (GCC 4.6.2) не может выполнить неявное преобразование типов, используя конструктор преобразования для версии шаблона класса. Это ожидаемое поведение? Есть ли обходной путь для этого, кроме создания вручную всех необходимых перегрузок?

Ответы [ 3 ]

12 голосов
/ 17 января 2012

Причина, по которой он не просто работает , заключается в том, что неявные преобразования типов (то есть через конструкторы) не применяются во время вывода аргумента шаблона.Но это работает, если вы сделаете внешний оператор другом с тех пор, как тип T известен, что позволяет компилятору исследовать, что можно привести, чтобы привести аргументы в соответствие.

Я сделал пример на основе вашего (но удалилC ++ 11 материал), вдохновленный Item 46 (класс рациональных чисел) в Scott Meyers Effective C ++ (ed 3).Ваш вопрос почти точно соответствует этому пункту.Скотт также отмечает, что ... «это использование друга не связано с доступом к закрытым частям класса».

Это также позволит работать со смесями foo , foo и т. д., если можно добавить T и U и т. д.

Также посмотрите этот пост: Неопределенность перегрузки C ++ *

#include <iostream>

using namespace std;

template< class T >
class foo
{
private:
   T _value;
public:
   foo() : _value() {}

   template <class U>
   foo(const foo<U>& that) : _value(that.getval()) {}

   // I'm sure this it can be done without this being public also;
   T getval() const { return _value ; }; 

   foo(const T& that) : _value(that) {}

   friend const foo operator +(foo &lhs,const foo &rhs) 
      {
     foo result(lhs._value+rhs._value); 
     return result;
      };
   friend const foo operator +(foo &lhs,const T &rhsval) 
      {
     foo result(lhs._value+rhsval); 
     return result;
      };
   friend const foo operator +(const T &lhsval,foo &rhs) 
      {
     foo result(lhsval+rhs._value); 
     return result;
      };

   friend foo& operator +=(foo &lhs,const foo &rhs)
      {
     lhs._value+=rhs._value;
     return lhs;
      };   
   friend std::ostream& operator<<(std::ostream& out, const foo& me){
      return out <<me._value;
   }
};

int main(){
   foo< int > f, g;
   foo< double > dd;
   cout <<f<<endl;
   f = f + g;
   cout <<f<<endl;
   f += 3 ;
   cout <<f<<endl;
   f = f + 5;
   cout <<f<<endl;
   f = 7 + f; 
   cout <<f<<endl;      
   dd=dd+f;
   cout <<dd<<endl;      
   dd=f+dd;
   cout <<dd<<endl;      
   dd=dd+7.3;
   cout <<dd<<endl;             
}
8 голосов
/ 17 января 2012

Я задал этот вопрос авторам библиотеки в MS и получил чрезвычайно информативный ответ от Стефана Лававея, поэтому я полностью доверяю этой информации.

Произошла ошибка компиляции, которую вы получите в случае шаблонак тому факту, что вывод аргумента шаблона выполняется до разрешения перегрузки, а вывод аргумента шаблона требует точных совпадений для добавления чего-либо к набору перегрузки.

В деталях вычитание аргумента шаблона просматривает каждую пару типа параметра P и типа аргумента A и пытается найти замены шаблона, которые приведут к точному совпадению A P. После нахождения совпадений для каждогоВ качестве аргумента он проверяет согласованность (поэтому, если вы вызываете bar(foo<T>, foo<T>) с T = int для первого параметра и T = double как вторым, это также дает сбой).Только после того, как в сигнатуре функции успешно подставлены согласованные совпадения, эта сигнатура добавляется в набор функций-кандидатов для разрешения перегрузки.

Только после того, как все обычные функции (найденные путем поиска по имени) и сигнатуры шаблонов соответствующих функций имеютв набор перегрузки был добавлен прогон разрешения перегрузки, после чего все эти сигнатуры функций оцениваются на «лучшее соответствие», в течение которого будут рассматриваться неявные преобразования.

Для случая operator+(foo<T>, foo<T>) сfoo<int> + 5, вычет аргумента шаблона не может найти подстановку для T, которая сделает выражение foo<T> точно match int, так что перегрузка operator + отбрасывается как кандидат, и неявное преобразование никогда не выполняетсядаже видно.

Мнение здесь, по-видимому, таково, что это, как правило, хорошая вещь, поскольку делает шаблоны намного более предсказуемыми, оставляя царство странного неявного поведения для разрешения перегрузки.

Стандартесть что сказать по этому поводу:

14.8.2.1 Вывод аргументов шаблона из вызова функции

"Вывод аргумента шаблона выполняется путем сравнения каждого типа параметра шаблона функции (назовите его P) с типом соответствующего аргумента вызова (назовите его A), как описано ниже....

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

* 1031Далее следует перечислить несколько особых случаев, когда в этом правиле есть исключения, включающие cv-квалификаторы (поэтому T & будет совместим с const T &), и сопоставление производных классов (в некоторых случаях оно может соответствовать Derived & и Base &), но в основном точноесоответствие - это правило.
2 голосов
/ 17 января 2012

Все возможные foo<T> являются одинаково действительными преобразованиями из int, поскольку конструктор принимает int, а не тип шаблона. Компилятор не может использовать другой параметр в операторе, чтобы угадать, какой из них вы имеете в виду, поэтому вы получите ошибку. Если вы прямо скажете ему, какой экземпляр вы хотите, я думаю, это сработает.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...