В вашем вопросе есть две проблемы, о которых я расскажу отдельно.
1. Поиск по названию.
В C++ неквалифицированные вызовы функций, такие как operator<<
в потоковом выражении, включают в себя unqualified name lookup, который в основном создает набор функций-кандидатов, которые вы можете вызывать. Из этого набора затем выбирается фактическая функция в соответствии с overload resolution rules. Для завершения вызова важно, чтобы (а) предполагаемая функция находилась в наборе кандидатов и (б), что она не является двусмысленной относительно других функций в наборе, учитывая способ вызова функции (количество предоставленных аргументов и их типы, явные параметры шаблона и т. д.). В вашем случае (а) не выполняется.
Проще говоря, поиск operator<<
выполняется в три этапа. Во-первых, компилятор ищет член operator<<
в классе правого операнда. Таким образом определяются операторы, определенные в потоках Boost.Log. Затем в пространствах имен, содержащих вызов функции, ищет оператор свободной позиции (не являющийся членом), начиная с внутренних пространств имен и перемещаясь наружу. Здесь также рассматриваются имена, импортированные с помощью using
директив и деклараций. Этот поиск заканчивается, как только будет найдено operator<<
. Обратите внимание, что рассмотренные пространства имен различаются, когда вы вызываете оператора из вашего кода и когда оператор вызывается из Boost.Log, когда вы используете log::add_value
. В первом случае вы импортируете my_space
в текущее пространство имен, поэтому ваш оператор найден. В последнем случае Boost.Log не импортирует ваше пространство имен, поэтому ваш оператор не найден.
Наконец, компилятор выполняет зависящий от аргумента поиск (ADL) для сбора дополнительной функции, которую вы можете вызвать. В принципе, он ищет оператора в ассоциированных пространствах имен, которые состоят из:
- Пространство имен, в котором объявляется тип каждого аргумента в вызове функции.Это означает, что пространство имен
std
рассматривается в обоих случаях, потому что там объявлено std::exception
. Когда используется Boost.Log, также рассматривается его внутреннее пространство имен, в котором объявлен тип потока (он содержит несколько операторов, но ни один из них не принимает std::exception
).
- Если функция является шаблоном, также рассматриваются пространства имен его типов аргументов шаблона.
- Если типы аргументов функции или типы аргументов шаблона являются самими шаблонами, эти пространства имен шаблонов аналогично рассматриваются. Это делается рекурсивно.
АДЛ находит operator<<
в пространстве имен std
, но ни один из них не принимает std::exception
. Чистый эффект от поиска оператора здесь заключается в том, что ваш оператор в my_space
найден только из-за вашего using
-directive, который не помогает, когда оператор вызывается из другой точки программы, например кода Boost.Log.
При внедрении операторов наилучшей практикой является использование ADL для поиска этих операторов. Это означает, что операторы, поддерживающие тип, должны быть помещены в одно и то же пространство имен, где объявлен этот тип. В случае std::exception
это пространство имен std
. Технически, добавление материала в пространство имен std
приводит к неопределенному поведению (согласно [namespace.std]/1), поэтому лучшим решением было бы определить ваш собственный манипулятор потока в вашем пространстве имен и использовать его для вывода исключений в потоки:
namespace my_space {
template< typename T >
struct my_manip
{
T const& value;
};
template< typename T >
my_manip<T> to_stream(T const& value) {
my_manip<T> m = { value };
return m;
}
template< typename CharT, typename TraitsT >
std::basic_ostream< CharT, TraitsT >& operator<< (
std::basic_ostream< CharT, TraitsT >& strm,
my_manip<std::exception> const& e)
{
// some printout stuff here
strm << e.value.what();
return strm;
}
} // namespace my_space
try {
// ...
}
catch (std::exception& e) {
std::cerr << my_space::to_stream(e) << std::endl;
}
Вы также можете предоставить обобщенное operator<<
для my_manip
, если хотите.
2. Добавление атрибутов Boost.Log.
Если ваше первоначальное намерение заключается в прикреплении исключений к записи журнала, я боюсь, что вы не делаете это правильно. Манифест add_value
не определяет тип времени выполнения предоставленного вами значения, что означает, что он сохраняет копию std::exception
, тем самым теряя любую диагностическую информацию, предоставляемую производным классом (включая сообщение what()
). Это называется object slicing.
Вы можете пройти несколько маршрутов, чтобы реализовать то, что вы хотите. Во-первых, вы можете отформатировать сообщение об ошибке в том месте, где вы попали в исключение.
catch (std::exception& e) {
MY_LOG_ERROR(slg) << e.what();
}
Вы не сможете использовать его как значение атрибута в раковинах или форматировщиках, но этого может быть достаточно для вас. Вы также можете использовать свой собственный манипулятор, конечно:
catch (std::exception& e) {
MY_LOG_ERROR(slg) << my_space::to_stream(e);
}
Если вам это нужно как значение атрибута, вы должны будете выбрать, какую информацию вы хотите. Например, если все, что вам нужно, это сообщение об ошибке, вы можете прикрепить ее вместо исключения:
catch (std::exception& e) {
MY_LOG_ERROR(slg) << log::add_value("ErrorMessage", std::string(e.what()));
}
Если само исключение это то, что вам нужно, то вам нужно C++ 11 exception_ptr
:
catch (std::exception& e) {
MY_LOG_ERROR(slg) << log::add_value("Exception", std::current_exception());
}
В C++ 03 вы можете использовать Boost.Exception или реализовать свой собственный механизм определения динамического типа исключения. Обратите внимание, что стандартная библиотека или Boost.Исключение также не обеспечивают operator<<
для exception_ptr
, поэтому для этого вам придется реализовать пользовательские форматирования.