Ошибка компиляции при вызове оператора<< для пользовательского типа из другого пространства имен

У меня есть тип во вложенном пространстве имен (abc::util), и он перегружен operator<<. Однако попытка вызвать его из внешнего пространства имен (abc) приводит к ошибке компиляции:

<source>: In function 'void abc::func(std::ostringstream&, const Foo&)':
<source>:38:9: error: no match for 'operator<<' (operand types are 'std::basic_ostream<char>' and 'const abc::Foo::MyType' {aka 'const abc::util::MyType'})
   37 |     oss << ';'
      |     ~~~~~~~~~~
      |         |
      |         std::basic_ostream<char>
   38 |         << foo.value;
      |         ^~ ~~~~~~~~~
      |                |
      |                const abc::Foo::MyType {aka const abc::util::MyType}

Вот минимальный пример:

#include <sstream>
#include <string>
#include <print>


namespace abc::util
{

struct MyType
{
    std::string value;
};

std::ostringstream&
operator<<( std::ostringstream& oss, const MyType& some_value )
{
    oss << some_value.value;
    return oss;
}

}

namespace abc
{

struct Foo
{
    using MyType = util::MyType;

    MyType value { "12345" };
};

void
func( std::ostringstream& oss, const Foo& foo )
{
    oss << ';'
        << foo.value; // does not compile
    
    // oss << ';';
    // oss << foo.value; compiles fine
}

}

int main( )
{
    std::ostringstream oss;
    abc::func( oss, abc::Foo { } );
    std::println( "{}", oss.view( ) );
}

Я точно не знаю, почему это компилируется:

    oss << ';';
    oss << foo.value;

но это не так:

    oss << ';'
        << foo.value;

Есть причины?

Проблема в том, что oss << ';' возвращает ostream&, а не ostringstream&. Это разрывает цепочку.

BoP 10.04.2024 11:08

Пространства имен не имеют значения. - там написано, что «типы операндов: 'std::basic_ostream<char>'...», и это не соответствует вашему прототипу. См. здесь по той же проблеме, только с глобальными именами. Есть ли у вас какая-то особая причина использовать только ostringstream?

molbdnilo 10.04.2024 11:18

Если вы измените ostringstream на ostream в func и в operator<<, это будет работать (oss в main может остаться ostringstream). Это также сделает их более общими. Есть ли причина не делать этого?

wohlstad 10.04.2024 11:25

@wohlstad Мне не нужно делать эти типы пригодными для печати, используя такие вещи, как std::cout или std::ofstream, поэтому я определил только перегрузки для ostringstream и istringstream.

digito_evo 10.04.2024 12:26

@digito_evo Я понимаю, но сделать ваш код более общим — это в целом хорошо. И в этом случае реализовать его не сложнее, плюс он решит вашу текущую проблему. Так что, ИМХО, у вас должна быть веская причина избегать этого.

wohlstad 10.04.2024 13:22

Добавил полный ответ, так как комментарии здесь эфемерны.

wohlstad 10.04.2024 17:56
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
77
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема:

oss << ';' возвращает ostream&, а не ostringstream&, поэтому вы не можете связывать вызовы при попытке:

oss << ';' << foo.value;

Обратите внимание, что пространства имен не имеют отношения к этой проблеме.

Решение:

Допустимым решением было бы изменить operator<< и func на использование std::ostream вместо std::ostringstream.

То есть:
Изменять:

std::ostringstream& operator<<( std::ostringstream& oss, const MyType& some_value)

К:

std::ostream& operator<<( std::ostream& oss, const MyType& some_value)

И изменить:

void func(std::ostringstream& oss, const Foo& foo)

К:

void func(std::ostream& oss, const Foo& foo)

Обратите внимание, что вы все равно можете использовать эти функции из main() с std::ostringstream oss, поскольку ostringstream является производным от ostream. Поэтому это решение только сделает ваш код более общим.

Последнее замечание:

В комментариях вы упомянули, что вам не нужно делать эти типы печатаемыми, используя такие вещи, как std::cout или std::ofstream, но я не понимаю, почему это является аргументом против этого решения:
Создание более общего кода обычно является положительным моментом.
Тот факт, что вам это не нужно (на данный момент или даже когда-либо), этого не меняет.
И в этом конкретном случае очень легко добиться более общей реализации.
Конечно, могли быть конкретные причины избегать обобщений (поэтому я и написал «обычно»), но вы их не указали.

Основная причина в том, что я не хочу, чтобы код намеренно или непреднамеренно записывал эти типы в файл через ofstream.

digito_evo 10.04.2024 18:32

Вы можете добавить assert(dynamic_cast<std::ostringstream&>(oss)); внутри func. Я признаю, что это не идеально и проблема будет обнаружена только во время выполнения, но на тот случай, если у вас нет лучшего решения.

wohlstad 10.04.2024 18:44

Что делает динамическое приведение типов в утверждении? Должен ли он выбрасываться при забросе?

digito_evo 10.04.2024 19:33
dynamic_cast<std::ostringstream&> вернет ноль, если oss не является ссылкой на ostringstream (или не является производным от него), а затем утверждение выскочит (поскольку оно эквивалентно false). Вы можете попробовать это, пройдя, например. std::ofstream. Как я уже сказал, меня не слишком устраивает это предложение, и решение на этапе компиляции (если вы его нашли) было бы лучше.
wohlstad 10.04.2024 19:40

На самом деле мне кажется, что это нормально. Утверждение отключено в сборках выпуска, поэтому меня устраивает это решение. Спасибо.

digito_evo 11.04.2024 01:36

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