Результат следующего кода, соответствующий стандарту ISO C++

#include <iostream>

template< typename U >
struct base {
    template< typename T >
    base const & operator<<( T x ) const {
        std::cout << sizeof( x ) << std::flush;
        return *this;
    }
};

template< typename U >
struct derived : public base< U > {
    using base<U>::operator<<;

    derived const & operator<<( float const & x ) const {
        std::cout << "derived" << std::flush;
        return *this;
    }
};

int main() {
    unsigned char c( 3 );
    derived< double > d;
    d << c;
    d.operator<<( c );
    return 0;
}

Не могли бы вы объяснить правила, необходимые для получения правильного ответа на приведенный выше код (перегрузка и переопределение в связи с шаблонами, интегральное продвижение, ...)? Это действительно? Если правила слишком длинные, предоставьте ссылки на литературу. Последние компиляторы расходятся во мнениях относительно правильного результата. gcc-4.6 и icpc-12.1.0 утверждают, что "11" является правильным ответом, но VS2010 отказывается компилировать d << c; из-за неоднозначности, но принимает d.operator<<( c );. Последний выводит 1 iirc. Так кто прав, а кто виноват?


person user1225999    schedule 22.02.2012    source источник
comment
Эй, какая разница? если d << c не компилируется в MSVC, то нужно напечатать только один 1. Другие компиляторы печатают два 1. Или я что-то упускаю?   -  person J.N.    schedule 22.02.2012
comment
Вы должны понимать, что ни один задействованный тип данных никогда не будет иметь sizeof == 1: 8, возможно, 4, конечно, 1, почему бы и нет. Но 11 действительно похоже на два 1.   -  person J.N.    schedule 22.02.2012
comment
что ты хочешь? Попробовать компилятор?   -  person Ade YU    schedule 22.02.2012
comment
@Дж.Н. c является аргументом для <<, а sizeof c == 1. А если программа не скомпилируется, то ничего и не напечатает.   -  person Mike Seymour    schedule 22.02.2012
comment
@MikeSeymour: да. Следовательно, MSVC печатает 1, а другие (вероятно, я не могу их здесь проверить) печатают 1 дважды. Что я пропустил ? (Редактировать, вам нужно прокомментировать первый <<, чтобы получить какой-то результат на MSVC, поэтому только одна печать).   -  person J.N.    schedule 22.02.2012
comment
Я предполагаю, что вопрос в том, почему у MSVC есть ошибка компилятора, где gcc не идет, и является ли это поведением, совместимым с ISO? Я не знаю ответа ни на один из них, если честно. Однако удачи.   -  person Dennis    schedule 22.02.2012


Ответы (2)


"11" - правильный вывод.

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

Точные правила довольно длинные. Подробности см. в разделе 13.3 Разрешение перегрузки текущего проекта стандарта C++, n3337, ссылка на который приведена в этот список документов рабочей группы.

Если вы спрашиваете, почему MSVC не компилирует один оператор, я не совсем уверен, но вызов функции неоднозначен, когда есть несколько вычисленных ICS, которые не лучше друг друга (как определено в 13.3.3). MSVC, кажется, вычисляет неверный ICS по крайней мере для одной из перегрузок в случае d << c, но диагностика не дает более подробной информации:

error C2666: 'derived<U>::operator <<' : 2 overloads have similar conversions
      with
      [
          U=double
      ]
      ConsoleApplication1.cpp(24): could be 'const derived<U> &derived<U>::operator <<(const float &) const'
      with
      [
          U=double
      ]
      ConsoleApplication1.cpp(14): or       'const base<U> &base<U>::operator <<<unsigned char>(T) const'
      with
      [
          U=double,
          T=unsigned char
      ]
      while trying to match the argument list '(derived<U>, unsigned char)'
      with
      [
          U=double
      ]
person bames53    schedule 22.02.2012
comment
Я считаю, что вы правы. Я думаю, что тонкость заключается в том, что С++ хочет выбрать функции, не являющиеся шаблонами, а не функции шаблона, но также хочет выбрать лучшую последовательность преобразования. В этом случае последовательность преобразования для шаблона лучше. 13.3.3.1 показано, когда нешаблонная функция считается лучше, чем шаблонная, и это применимо только в том случае, если нешаблонная функция не имеет худшей последовательности преобразования. Однако в этом случае нешаблонная функция имеет худшую последовательность преобразования. - person Vaughn Cato; 22.02.2012

Он не компилируется, потому что вы просите, чтобы operator << вызывался автоматически. Это как иметь operator +, а также иметь оператор преобразования, который может преобразовывать в базовый тип (скажем, в int). Например:

class Conversion
{
public:
    operator int()
    {
        return 5;
    }

    int operator +(int x)
    {
        return 10;
    }
};

И используя его как:

Conversion conv;
conv + 1.0;

Здесь operator + не может быть вызвано неявно, так как operator int также возможно. Если вы пройдете, int, однако, будет вызван operator +(int). Чтобы вызвать оператор + с double, мы можем сделать:

conv.operator+(1.0);

Я не знаю правил компиляции и определения строгих стандартов.

Я также обнаружил, что если мы изменим классы base и derived как классы без шаблонов, проблема все равно останется (в VC10/11):

struct base {   
    base const & operator<<( int x ) const { 
        std::cout << sizeof( x ) << std::flush; 
        return *this; 
    } 
}; 

struct derived : public base{ 
    using base::operator<<; 
    derived const & operator<<( float const & x ) const { 
        std::cout << "derived" << std::flush; 
        return *this; 
    } 
}; 

int main()
{
  derived d;
  d<<10.0;  // ERROR
}
person Ajay    schedule 22.02.2012