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

StackOverflow https://stackoverflow.com/questions/811724

Вопрос

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

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

M2Stream() << "the string";

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

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

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

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

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

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

#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
Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

Первая проблема вызвана странными и хитрыми правилами языка 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 первый.

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

M2Stream ms;
ms << "the string";

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

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

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

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

M2Stream & operator<<( void *vp )

должно быть:

M2Stream & operator<<( const void *vp )

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

#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;
}

Вы могли бы использовать перегрузку, подобную этой:

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top