Невозможно найти оператор с помощью неявного преобразования в C ++

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

В приведенном ниже коде класс-оболочка управляет объектом, выделенным в куче, и неявно преобразуется в ссылку на этот объект. Это позволяет передавать объект-оболочку в качестве аргумента функции write (...), поскольку имеет место неявное преобразование.

Однако компилятор терпит неудачу при попытке разрешить вызов оператора ‹< (...), если не сделано явное приведение (проверено с компиляторами MSVC8.0, Intel 9.1 и gcc 4.2.1).

Итак, (1) почему неявное преобразование терпит неудачу в этом случае? (2) может ли это быть связано с поиском, зависящим от аргументов? и (3) можно ли что-нибудь сделать, чтобы эта работа работала без явного приведения?

#include <fstream>

template <typename T>
class wrapper
{
    T* t;
  public:
    explicit wrapper(T * const p) : t(p) { }
    ~wrapper() { delete t; }
    operator T & () const { return *t; }
};

void write(std::ostream& os)
{
    os << "(1) Hello, world!\n";
}

int main()
{
    wrapper<std::ostream> file(new std::ofstream("test.txt"));

    write(file);
    static_cast<std::ostream&>( file ) << "(2) Hello, world!\n";
    // file << "(3) This line doesn't compile!\n";
}

person Simon C.    schedule 19.02.2009    source источник
comment
Мне совершенно не хватает значения wrapper: объекты fstream, созданные в стеке, очищаются после себя. Вместо этого вы создаете объект fstream в куче, а затем используете оболочку, чтобы гарантировать, что он будет очищен точно в том же месте, где он был бы очищен с самого начала.   -  person Max Lybbert    schedule 20.02.2009
comment
В реальной версии я использую boost :: shared_ptr ‹T› вместо T *, так что экземпляры являются общими и реализуют необходимую семантику для общего экземпляра. Пример сильно упрощен и не очень реалистичен, но его достаточно, чтобы показать проблему.   -  person Simon C.    schedule 20.02.2009
comment
набор преобразований ограничен, если аргументы для шаблона автоматически расшифровываются. так что в вашем случае, если у вас будет шаблон ‹typename A, typename B› void write (basic_ostream ‹A, B› &); вы столкнетесь с той же проблемой (в дополнение к поиску, зависящему от аргумента, в случае op ‹*)   -  person Johannes Schaub - litb    schedule 20.02.2009


Ответы (5)


Это не удается, потому что вы пытаетесь разрешить оператор вашего класса wrapper<T>, который не существует. Если вы хотите, чтобы он работал без приведения, вы можете собрать что-то вроде этого:

template<typename X> wrapper<T> &operator <<(X &param) const {
    return t << param;
}

К сожалению, я не знаю способа разрешить возвращаемый тип во время компиляции. К счастью, в большинстве случаев это тот же тип, что и объект, включая в данном случае ostream.

РЕДАКТИРОВАТЬ: измененный код по предложению dash-tom-bang. Тип возврата изменен на wrapper<T> &.

person zildjohn01    schedule 19.02.2009
comment
›Для определения типа возвращаемого значения во время компиляции. Это потребует более сложного метапрограммирования шаблонов ... плюс (вероятно) следы типов плюс SFINAE. - person Richard; 20.02.2009
comment
Это может быть хорошим обходным путем, но следующее работает, даже если оператор + = не определен для оболочки ‹int›: wrapper ‹int› i (new int (10)); я + = 1; // дает 11 - person Simon C.; 20.02.2009
comment
Случай int может работать из-за того, что он является интегральным типом, где продвижение / преобразование всего типа происходит более легко? Например. если я + = 'int, может быть, он также пытается превратить lhs в int? Мне нравится этот ответ, вероятно, так и будет, хотя я бы, наверное, вернул оболочку ‹T› вместо этого - person dash-tom-bang; 20.02.2009
comment
Хороший звонок, даш-том-бэнг. Я знал это :) - person zildjohn01; 20.02.2009
comment
Большая разница в том, что + = - оператор присваивания. - person Ismael; 20.02.2009
comment
ваш op ‹< теперь сломан с возвращением обертки ‹T›. op ‹* потока вернет T &, но вы вернете обертку ‹T› &, которая будет кусаться. вы, вероятно, хотели сделать * t ‹< param; вернуть * это; - person Johannes Schaub - litb; 17.03.2009

У компилятора недостаточно контекста, чтобы определить, что operator& выполнит допустимое преобразование. Итак, да, я думаю, что это связано с поиском, зависящим от аргументов: компилятор ищет operator<<, который может принимать не-const wrapper<std::ostream> в качестве своего первого параметра.

person greyfade    schedule 19.02.2009

Я думаю, проблема связана с сохранением некоторых ограничений времени компиляции. В вашем примере компилятор сначала должен будет найти все возможные операторы ‹<. Затем, для каждого из них, он должен попробовать, можно ли автоматически преобразовать ваш объект (прямо или косвенно) в любой из типов, которые может принять каждый оператор ‹*.

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

person Diego Sevilla    schedule 19.02.2009

После некоторого тестирования, еще более простой пример определяет источник проблемы. Компилятор не может вывести аргумент шаблона T в f2(const bar<T>&) ниже из неявного преобразования wrapper<bar<int> > в bar<int>&.

template <typename T>
class wrapper
{
    T* t;
  public:
    explicit wrapper(T * const p) : t(p) { }
    ~wrapper() { delete t; }
    operator T & () const { return *t; }
};

class foo { };

template <typename T> class bar { };

void f1(const foo& s) { }
template <typename T> void f2(const bar<T>& s) { }
void f3(const bar<int>& s) { }

int main()
{
    wrapper<foo> s1(new foo());
    f1(s1);

    wrapper<bar<int> > s2(new bar<int>());
    //f2(s2); // FAILS
    f2<int>(s2); // OK
    f3(s2);
}

В исходном примере std::ostream на самом деле typedef для шаблонного класса std::basic_ostream<..>, и такая же ситуация применяется при вызове шаблонной функции operator<<.

person Simon C.    schedule 20.02.2009

Проверьте подпись оператора вставки ... Я думаю, они берут неконстантную ссылку на поток?

Подтверждено стандартом C ++ 03, сигнатура оператора вывода char *:

template<class charT, class traits>
basic_ostream<charT,traits>& operator<<(basic_ostream<charT,traits>&, const charT*);

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

Как отмечено в комментарии: это не имеет значения.

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

person Richard    schedule 19.02.2009
comment
Спасибо, но без разницы. Оператор преобразования является константной функцией-членом, поскольку он не изменяет указатель t, но преобразование по-прежнему выполняется в T &, а не в const T &. Это похоже на разницу между 'const T *' и 'T * const'. - person Simon C.; 20.02.2009
comment
В последнее время я обвиняю недостаточно C ++. - person Richard; 20.02.2009