Почему компилятор Visual C ++ вызывает неправильную перегрузку здесь? - PullRequest
3 голосов
/ 01 мая 2009

Почему компилятор Visual C ++ вызывает неправильную перегрузку здесь?

У меня есть подкласс ostream, который я использую для определения буфера для форматирования. Иногда я хочу создать временный объект и сразу вставить в него строку с помощью обычного оператора <<, например: </p>

M2Stream() << "the string";

К сожалению, программа вызывает перегрузку члена оператора << (ostream, void *) вместо оператора << (ostream, const char *), не являющегося членом. </p>

Я написал приведенный ниже пример в качестве теста, в котором я определяю свой собственный класс M2Stream, который воспроизводит проблему.

Я думаю, что проблема в том, что выражение M2Stream () создает временное значение, и это как-то заставляет компилятор предпочесть перегрузку void *. Но почему? Это подтверждается тем фактом, что, если я сделаю первый аргумент для перегрузки без членов const M2Stream &, я получу неоднозначность.

Другая странная вещь заключается в том, что она вызывает желаемую перегрузку const char *, если я сначала определяю переменную типа const char *, а затем вызываю ее вместо буквенной строки char, например:

const char *s = "char string variable";
M2Stream() << s;  

Как будто литеральная строка имеет тип, отличный от переменной const char *! Разве они не должны быть одинаковыми? И почему компилятор вызывает вызов перегрузки void *, когда я использую временную и буквенную строку символов?

#include "stdafx.h"
#include <iostream>
using namespace std;


class M2Stream
{
public:
    M2Stream &operator<<(void *vp)
    {
        cout << "M2Stream bad operator<<(void *) called with " << (const char *) vp << endl;
        return *this;
    }
};

/* If I make first arg const M2Stream &os, I get
\tests\t_stream_insertion_op\t_stream_insertion_op.cpp(39) : error C2666: 'M2Stream::operator <<' : 2 overloads have similar conversions
        \tests\t_stream_insertion_op\t_stream_insertion_op.cpp(13): could be 'M2Stream &M2Stream::operator <<(void *)'
        \tests\t_stream_insertion_op\t_stream_insertion_op.cpp(20): or 'const M2Stream &operator <<(const M2Stream &,const char *)'
        while trying to match the argument list '(M2Stream, const char [45])'
        note: qualification adjustment (const/volatile) may be causing the ambiguity
*/
const M2Stream & operator<<(M2Stream &os, const char *val)
{
    cout << "M2Stream good operator<<(const char *) called with " << val << endl;
    return os;
}


int main(int argc, char argv[])
{
    // This line calls void * overload, outputs: M2Stream bad operator<<(void *) called with literal char string on constructed temporary
    M2Stream() << "literal char string on constructed temporary";

    const char *s = "char string variable";

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
    M2Stream() << s;  

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
    M2Stream m;
    m << "literal char string on prebuilt object";
    return 0;
}

Выход:

M2Stream bad operator<<(void *) called with literal char string on constructed temporary
M2Stream good operator<<(const char *) called with char string variable
M2Stream good operator<<(const char *) called with literal char string on prebuilt object

Ответы [ 5 ]

10 голосов
/ 01 мая 2009

Компилятор делает правильные вещи: Stream() << "hello"; должен использовать operator<<, определенный как функция-член. Поскольку временный объект потока не может быть привязан к неконстантной ссылке, а только к константной ссылке, оператор, не являющийся членом, который обрабатывает char const*, не будет выбран.

И он спроектирован таким образом, как вы видите при смене оператора. Вы получаете неоднозначности, потому что компилятор не может решить, какой из доступных операторов использовать. Потому что все они были разработаны с учетом отказа от неучастия operator<< для временных.

Тогда, да, строковый литерал имеет тип, отличный от char const*. Строковый литерал - это массив константных символов. Но это не имеет значения в вашем случае, я думаю. Я не знаю, какие перегрузки operator<< MSVC ++ добавляет. Разрешается добавлять дополнительные перегрузки, если они не влияют на поведение допустимых программ.

Почему M2Stream() << s; работает, даже если первый параметр является неконстантной ссылкой ... Ну, у MSVC ++ есть расширение, которое позволяет неконстантным ссылкам связываться с временными файлами. Установите уровень предупреждения на уровне 4, чтобы увидеть предупреждение об этом (что-то вроде «используется нестандартное расширение ...»).

Теперь, поскольку есть оператор-член <<, который принимает <code>void const*, и char const* может преобразовать в него, этот оператор будет выбран, и адрес будет выведен так, как это является перегрузкой void const* для.

Я видел в вашем коде, что у вас действительно перегрузка void*, а не void const*. Ну, строковый литерал может конвертироваться в char*, даже если тип строкового литерала равен char const[N] (где N - количество символов, которое вы вводите). Но это преобразование устарело. Не должно быть стандартным, чтобы строковый литерал конвертировался в void*. Мне кажется, это еще одно расширение компилятором MSVC ++. Но это объясняет, почему строковый литерал обрабатывается иначе, чем указатель char const*. Вот что говорит Стандарт:

Строковый литерал (2.13.4), который не является широким строковым литералом, может быть преобразован в значение типа "указатель на символ"; широкий строковый литерал может быть преобразован в значение типа "указатель на wchar_t". В любом случае результатом является указатель на первый элемент массива. Это преобразование рассматривается только при наличии явного соответствующего целевого типа указателя, а не при общей необходимости преобразования из lvalue в rvalue. [Примечание: это преобразование устарело. См. Приложение D.]

5 голосов
/ 01 мая 2009

Первая проблема вызвана странными и хитрыми правилами языка C ++:

  1. Временная переменная, созданная вызовом конструктора, является rvalue .
  2. Значение r не может быть связано с неконстантной ссылкой.
  3. Однако у объекта rvalue могут быть вызваны неконстантные методы.

Что происходит, так это то, что ostream& operator<<(ostream&, const char*), функция, не являющаяся членом, пытается связать созданный вами временный M2Stream с неконстантной ссылкой, но это не удается (правило № 2); но ostream& ostream::operator<<(void*) является функцией-членом и поэтому может связываться с ней. При отсутствии функции const char* она выбирается как лучшая перегрузка.

Я не уверен, почему разработчики библиотеки IOStreams решили сделать operator<<() для void* методом, а не operator<<() для const char*, но это так, поэтому у нас есть эти странные несоответствия иметь дело.

Я не уверен, почему возникает вторая проблема. У вас одинаковое поведение в разных компиляторах? Возможно, это ошибка компилятора или стандартной библиотеки C ++, но я бы оставил это как оправдание в крайнем случае - по крайней мере, посмотрим, сможете ли вы повторить поведение с обычным ostream первым.

1 голос
/ 01 мая 2009

Проблема в том, что вы используете временный объект потока. Измените код на следующий, и он будет работать:

M2Stream ms;
ms << "the string";

По сути, компилятор отказывается связывать временную ссылку с неконстантной ссылкой.

Что касается вашего второго замечания о том, почему он связывается, когда у вас есть объект "const char *", я считаю, что это ошибка в компиляторе VC. Я не могу сказать наверняка, однако, когда у вас есть только строковый литерал, происходит преобразование в 'void *' и преобразование в 'const char *'. Если у вас есть объект 'const char *', то для второго аргумента преобразование не требуется - и это может быть триггером для нестандартного поведения VC, чтобы разрешить привязку не const ref.

Я полагаю, что 8.5.3 / 5 - это раздел стандарта, который охватывает это.

0 голосов
/ 01 мая 2009

Вы можете использовать такую ​​перегрузку, как эта:

template <int N>
M2Stream & operator<<(M2Stream & m, char const (& param)[N])
{
     // output param
     return m;
}

В качестве дополнительного бонуса теперь вы знаете, что N - это длина массива.

0 голосов
/ 01 мая 2009

Я не уверен, что ваш код должен компилироваться. Я думаю:

M2Stream & operator<<( void *vp )

должно быть:

M2Stream & operator<<( const void *vp )

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

#include <iostream>
using namespace std;


class M2Stream
{
};

const M2Stream & operator<<( const M2Stream &os, const char *val)
{
    cout << "M2Stream good operator<<(const char *) called with " << val << endl;
    return os;
}


int main(int argc, char argv[])
{
    M2Stream() << "literal char string on constructed temporary";

    const char *s = "char string variable";

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with char string variable
    M2Stream() << s;  

    // This line calls the const char * overload, and outputs: M2Stream good operator<<(const char *) called with literal char string on prebuilt object
    M2Stream m;
    m << "literal char string on prebuilt object";
    return 0;
}
...