C++ Перегрузка операторов I/0: преодоление неоднозначности

Я нахожусь в процессе работы над классом, и у меня есть 3 массива в моем классе, где все они представляют одни и те же данные, но в другом формате. У меня перегрузка << оператор, который объявлен за пределами моего класса, который использует const-ссылку на него, а не как друга этого класса.

SomeClass {
public:
    // Nameless Union - All 3 Arrays Are of The Same Exact Data Type
    // And All 3 Arrays Have The Same Exact Size. This Nameless Union
    // Uses The Same Memory Address For All 3 Arrays And Their Elements. 
    // So An Element Is Changed By One Array Type, It Is Expected And 
    // Accepted For It To Change The Others. This Is Not 3 Different 
    // Arrays, This Is Still 1 Array Of Size 256, Just Different 
    // Representations Or Different Ways To Access Them.
    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };

    SomeClass() { std::fill( std::begin( m_256 ), std::end( m_256 ), 0 ); }

}; // SomeClass

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;

    for ( unsigned box = 0; box < 4; box++ ) {
        for ( unsigned slice = 0; slice < 4; slice++ ) {
            for ( unsigned row = 0; row < 4; row++ ) {
                for ( unsigned col = 0; col < 4; col++ ) {
                    out << "(" << box << "," << slice << "," << row << "," << col << ") = "
                         << c.m_4[box][slice][row][col] << std::endl;
                }
            }
        }
    } 
    return out;
} // operator<<

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

Я знаю, что вы не можете сделать это: добавив 2-й

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;
    for ( unsigned i = 0; i < 16; i++ ) {
        for ( unsigned j = 0; j < 16; j++ ) {
            out << "(" << i << "," << j << ") = " << c.m_16[i][j] << std::endl;
        }
    }
    return out;
} // operator<<

И третий

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) {
    out << std::endl;
    for ( unsigned u = 0; u < 256; u++ ) {
        out << u << " = " << m_256[u] << std::endl;
    }
    return out;
} // operator<<

В связи с тем, что это неоднозначно. Тем не менее, я хотел бы иметь функциональность для отображения в любом из 3 различных форматов.

Есть ли какие-либо рабочие раунды или решения этой проблемы? Я хотел бы иметь возможность просто отправить объект класса оператору потока, и эти типы операторов не могут принимать дополнительные параметры, поскольку они являются бинарными операторами, а не функцией.

5 ответов

Вы можете просто использовать класс адаптера для записи вывода. Вы можете передать спецификатор формата в конструктор или дифференцировать по типу. Например (дифференциация по типу):

struct SomeClassAs256 {
  SomeClass const& x_;

  explicit(SomeClass const& x) : x_(x) {}
};

И тогда есть оператор<< реализация:

ostream& operator<<(ostream& os, SomeClassAs256 const& x) {
  ...
  return os;
}

И тогда вы используете это:

SomeClass x;
...
cout << SomeClassAs256(x) << endl;

Вы не можете сделать это, по крайней мере, не так просто.

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

Создать набор функций форматирования, который возвращает строку, которая затем используется для вывода, просто с помощью std::ostringstream:

std::string format1(SomeClass const& c)
{
    std::ostringstream os;
    os << whatever you want ...
    return os.str();
}

Создание структур манипуляторов немного сложнее, но также может быть более гибким и мощным:

class format1
{
public:
    format1(SomeClass const& c)
        : c_(c)
    {}

    friend std::ostream& operator<<(std::ostream& os,
                                    format1 const& fmt)
    {
        os << some formated output here using `fmt.c_`...;
        return os;
    }

private:
    SomeClass const& c_;
};

В обоих случаях вы можете использовать его одинаково:

SomeClass c(...);
std::cout << format1(c) << '\n';

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

Направление, которое я выбрал, было таким; Я добавил enum в мой класс непосредственно с 3 типами. Я добавил публичную функцию, которая выводит строку и принимает enum введите в качестве параметра. Я добавил ostream operator<< в мой класс и параметр, который он принимает, является typename на мои занятия enum, Я использую функцию out, чтобы разветвлять мои 3 различных способа отображения информации. Так что теперь в другом разделе кода, который использует этот объект, я могу передать экземпляр, вызывающий функцию out, которая возвращает строку, передавая требуемый тип. Мой класс теперь выглядит так:

class SomeClass {
public:
    enum OutputType { x256, x16, x4 };

    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };

    std::string out( OutputType type ) const;

    std::ostream& operator<<( typename SomeClass::OutputType type );

}; // SomeClass


std::ostream& SomeClass::operator<<( typename SomeClass::OutputType type ) {
    return std::ostream << out(type );
} // operator<<

std::string SomeClass::out( OutputType type ) const {
    std::ostringstream out;
    out << std::endl;

    switch( type ) {
        case: x256: {
            // Print Format Here
            break; 
        }
        case x16: {
            // Print Format Here
            break;
        }
        case x4: {
            // Print Format Here
            break;
        }
        default: {
            // Error Message
            return out.str();
        }
    }
    return out.str();
 } // out

Я не знаю, связано ли это с моим классом в моем проекте, будучи template или как operator<< реализовано, но я должен был использовать typename с в объявлении / определении функции для его работы, так что это выглядит в моем коде, за исключением имени класса.

template< class T>
std::ostringstream& SomeClass<T>::operator<<( typename SomeClass<T>::Type type ) { 
    // Code Here
}

Я ценю всю помощь и предложения, которые вы все предложили, и я принимаю тот совет, который был дан всем сердцем, спасибо всем.

редактировать

Теперь, если я хочу сделать это немного проще для пользователя: я мог бы переместить свою функцию out в приватный раздел; Напишите 3 обертки или напечатайте функции, которые не принимают никаких параметров, но они устанавливают переменную в закрытый метод.

Добавьте некоторый способ, например, член к вашему классу, чтобы решить выходной формат:

public:
    enum OutputStyle 
    {
        M_256,
        M_16,
        M_4,
    };
    OutputStyle style() const {return style_;}

 private:  
     mutable OutputStyle style_ = M_256;

Добавьте некоторый способ, например, оператор вызова функции, чтобы установить это:

public:
    SomeClass const& operator()(OutputStyle s) const
    {
        style_ = s;
        return *this;
    }

Сделайте оператор <<, чтобы рассмотреть это:

std::ostream& operator<<( std::ostream& out, const SomeClass& c ) 
{
    switch( c.style() )
    {
    default:
        assert(!"defective operator <<");
    case SomeClass::M_256:
        // ... output like M_256 here 
        break; 
    case SomeClass::M_16: 
        // ... output like M_16 here
        break; 
    case SomeClass::M_4:
        // ... output like M_4 here
        break;
    }
} 

Затем вы можете изменить его до или во время вывода:

    SomeClass x; // <- has style M_256

    x(SomeClass::M_16);

    std::cout << "current:" << x << std::endl
              << "with M_4:" << x(SomeClass::M_4) << std::endl;

Вы можете сделать это, возвращая прокси (для обеспечения адаптации) из вашего класса, используя функции make и используя: operator << (ostream, SomeClass::Proxy) в качестве оператора вывода. Я поработаю над примером кода.

Таким образом, вам не нужно выставлять внутренности вашего класса тоже. Ничего плохого в том, чтобы сделать оператора << друзьями...

Пример:

#include <iostream>
class SomeClass {
    union {
        int m_256[256];
        int m_16[16][16];
        int m_4[4][4][4][4];
    };
public:

    SomeClass() { std::fill( std::begin( m_256 ), std::end( m_256 ), 0 ); }

    struct x256
    {
      const SomeClass& c_;
      explicit x256(const SomeClass& c): c_(c)
      {
      }
    };
    struct x16
    {
      const SomeClass& c_;
      explicit x16(const SomeClass& c): c_(c)
      {
      }
    };

    struct x4
    {
      const SomeClass& c_;
      explicit x4(const SomeClass& c): c_(c)
      {
      }
    };

    x256 output265() const
    {
      return x256(*this);
    }

    x16 output16() const
    {
      return x16(*this);
    }

    x4 output4() const
    {
      return x4(*this);
    }

    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x256& c ) {
        out << std::endl;
        for ( unsigned u = 0; u < 256; u++ ) {
            out << u << " = " << c.c_.m_256[u] << std::endl;
        }
        return out;
    } // operator<<
    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x16& c ) {
        //...
        return out;
    } // operator<<
    friend std::ostream& operator<<( std::ostream& out, const SomeClass::x4& c ) {
        //...
        return out;
    } // operator<<
}; // SomeClass

void testSomeClass()
{
  SomeClass someClass;

  std::cout << someClass.output265() << someClass.output16() << someClass.output4() << std::endl;
}
Другие вопросы по тегам