Неоднозначная перегрузка

Доброго времени суток
Пусть имеется класс A, для которого перегружен оператор +, конструктор A(int) и оператор приведения к bool (для использования в конструкциях типа if( Aobject ) {..}).
При использовании класса появляется проблема. В конструкциях типа Apbject + 5 появляется неоднозначность: можно привести Aobject к bool и сложить два int'a, а можно привести 5 к A и сложить два объекта класса A (благо оператор + перегружен). Как грамотно избежать такой неоднозначности?

Проблема пока имеет теоретический интерес или уже практический? Если практический — код в студию!

Интерес практический. Делаю класс для реализации длинных беззнаковых чисел (до 2^128):
Файл ulll.h:

#ifndef ULLL_H
#define ULLL_H

//ulll - класс для работы с 128-битовыми числами

#include <iostream>
#include <string>

//Макрос для задания в коде констант больших чисел
#define ULLL( num ) std::string(#num)

typedef unsigned long long uqword_t;

class ulll
{

    //Перегруженные операторы
    friend ulll operator +( const ulll &op1, const ulll &op2 );
    friend ulll operator -( const ulll &op1, const ulll &op2 );
    friend ulll operator *( const ulll &op1, const ulll &op2 );
    friend ulll operator /( const ulll &op1, const ulll &op2 );
    friend ulll operator %( const ulll &op1, const ulll &op2 );

    friend bool operator >( const ulll &op1, const ulll &op2 );
    friend bool operator <( const ulll &op1, const ulll &op2 );
    friend bool operator ==( const ulll &op1, const ulll &op2 );
    friend bool operator !=( const ulll &op1, const ulll &op2 );

    friend std::ostream &operator <<( std::ostream &stream, const ulll &op );
    friend std::istream &operator >>( std::istream &stream, ulll &op );

public:
    //Инициализация только младшей части
    ulll( uqword_t val = 0 ):
        high( 0 ),
        low( val )
    {}

    //Инициализация строкой
    ulll( const std::string &num )
    {
        *this = strtoulll( num.c_str() );
    }

    ulll &operator ++() { return ( *this = *this + 1 ); }
    ulll operator ++( int ) { ulll old = *this; ++( *this ); return old; }
    ulll &operator --() { return ( *this = *this - 1 ); }
    ulll operator --( int ) { ulll old = *this; --( *this ); return old; }

    ulll &operator +=( const ulll &op ) { return ( *this = *this + op ); }
    ulll &operator -=( const ulll &op ) { return ( *this = *this - op ); }
    ulll &operator *=( const ulll &op ) { return ( *this = *this * op ); }
    ulll &operator /=( const ulll &op ) { return ( *this = *this / op ); }
    ulll &operator %=( const ulll &op ) { return ( *this = *this % op ); }

    //Преобразования для использования в конструкциях типа if( num )
    operator bool() const { return !( low == 0 && high == 0 ); }


    uqword_t getH() { return high; }
    uqword_t getL() { return low; }

private:

    //Старшие и младшие 8 байт числа
    uqword_t high;
    uqword_t low;

    //Преобразование строки в число
    ulll strtoulll( const std::string &num );

};


#endif // ULLL_H

Ну и файл ulll.cpp, соответственно:

#include "ulll.h"

#include <cstring>

ulll operator +( const ulll &op1, const ulll &op2 )
{
    ulll ret;

    //Складываем по частям. Сначала младшие (переносим флаг переполнения), а затем старшие части

    ret.low = op1.low + op2.low;
    ret.high = op1.high + op2.high;

    //Если переполнение, добавляем перенесённый бит к сумме старших частей
    if ( ret.low < op1.low )
    {
        ret.high++;
    }
    return ret;    
}

ulll operator -( const ulll &op1, const ulll &op2 )
{
    //Вычитание так же происходит по частям
    ulll ret;

    ret.low = op1.low - op2.low;
    ret.high = op1.high - op2.high;

    if ( ret.low > op1.low )
    {
        ret.high--;
    }

    return ret;
}

ulll operator *( const ulll &op1, const ulll &op2 )
{
    //Реализация через сложение. На всякий случай

    ulll ret = op1, mul = op2;

    for ( ; mul; mul-- )
    {
        ret += op1;
    }

    return ret;
}

ulll operator /( const ulll &op1, const ulll &op2 )
{
    ulll temp = op1, ret;

    while( temp > op2 )
    {
        temp -= op2;
        ret++;
    }

    return ret;
}

ulll operator %( const ulll &op1, const ulll &op2 )
{
    ulll ret = op1;

    while( ret > op2 )
    {
        ret -= op2;
    }

    return ret;
}

bool operator >( const ulll &op1, const ulll &op2 )
{
    return ( op1.high > op2.high ) || ( ( op1.high == op2.high ) && ( op1.low > op2.low ) );
}

bool operator <( const ulll &op1, const ulll &op2 )
{
    return !( op1 > op2 );
}

bool operator ==( const ulll &op1, const ulll &op2 )
{
    return ( op1.high == op2.high ) && ( op1.low == op2.low );
}

bool operator !=( const ulll &op1, const ulll &op2 )
{
    return !( op1 == op2 );
}

std::ostream &operator <<( std::ostream &stream, const ulll &op )
{
    //Преобразуем в строку и выводим строку

    if ( op )
    {
        std::string str;

        ulll temp = op;

        while ( temp != 0 )
        {
            str += '0' + static_cast<char>( temp % 10 );
            temp /= 10;
        }

        for ( int i = str.size() - 1; i >= 0; i-- )
            stream << str[i];

        return stream;
    }
    return stream << "0";
}

std::istream &operator >>( std::istream &stream, ulll &op )
{
    std::string temp;
    stream >> temp;
    op = op.strtoulll( temp );
    return stream;
}

ulll ulll::strtoulll( const std::string &num )
{
    ulll ret;

    //Переводим строку в число классическим способом
    for ( int i = num.size() - 1; i >= 0; i-- )
    {
        //Текущий разряд
        ulll temp = 1;

        for ( int j = i; j > 0; j-- )
            temp *= 10;

        temp *= ( num[i] - '0' );

        ret += temp;
    }

    return ret;
}

Сама неоднозначность возникает в строках:

 ulll &operator ++() { return ( *this = *this + 1 ); }
 ulll &operator --() { return ( *this = *this - 1 ); }
 while ( temp != 0 )
 str += '0' + static_cast<char>( temp % 10 );

porshe, запретить неявное преобразование bool к int невозможно.

Поэтому пути два:

  1. либо отказаться от оператора ull::operator bool() const,
  2. либо при всех неоднозначностях явно вызывать нужную функцию или явно приводить операнд(ы) к нужному типу. Например:
 ulll &operator ++() { return ( *this = *this + ulll(1) ); }
 ulll &operator --() { return ( *this = operator -(*this, 1) ); }
 while ( temp != (ulll)0 )
 str += '0' + static_cast<char>((temp % ulll(10)).getL());  // здесь у тебя ещё одна ошибка со static_cast<char>

На мой вкус проще первый вариант.

отказаться от оператора ull::operator bool() const

Ну, этот вариант я уже рассматривал. Что же делать с if( object )?

Кстати говоря, если твой объект представляет собой числовую величину, то запись if ( object != 0 ) как-то более уместна, чем if( object ): «если число не равно нулю, то...» против «если число, то...».

porshe, для этого, начиная с C++11, имеется explicit operator bool:

explicit operator bool() const { return !( low == 0 && high == 0 ); }

Действительно, в стандарте п.7.1.2 есть

6 The explicit specifier shall be used only in the declaration of a constructor or conversion function within its class definition; see 12.3.1 and 12.3.2.

А в хелпе к MS VC++ написано только про конструкторы ((

The explicit keyword can only be applied to in-class ctor declarations to explicitly construct an object.

Несмотря на это, компилятор от MS таки поддерживает спецификатор explicit для функций приведения типа для классов.

А ведь у меня по началу была мысль про explicit. Надо было в стандарт смотреть.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

Пожалуйста, оформляйте исходный код в соответствии с правилами разметки. Для того, чтобы вставить код в комментарий, скопируйте его в текстовое поле ниже, после чего выделите то, что скопировали и нажмите кнопку «код» в панели инструментов. Иначе ваш код может принять нечитаемый вид.

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.