C++ Standard Library Gotcha: Include Headers Explicitly
Jan 24 2016
I was using the boost::log
library component and when compiling my code, I ran into a rather odd error converting a derived shared pointer to a base class shared pointer, i.e. implicitly converting a shared_ptr<std::ofstream>
to a const shared_ptr<std::ostream> &
. Here's the code snippet, with the line causing the compiler error highlighed. Note that code has been simplified for clarity.
using namespace boost::log; typedef sinks::asynchoronous_sink< sinks::text_ostream_backend> TextSink; bool Initialize(const std::string &logFile) { bool didSucceed = false; try { auto sink = boost::make_shared<TextSink>(); auto lfstream = boost::make_shared<std::ofstream>(logFile); sink->locked_backend()->add_stream(lfstream); // <-- COMPILER ERROR HERE core::get()->add_sink(sink); BOOST_LOG(lgr_gtl) << "LogSystem initialized..."; core::get()->flush(); didSucceed = true; } catch (const std::exception& ex) { std::cout << "LogSystem::Initialize() failed." << std::endl; std::cout << "Details:" << std::endl; std::cout << ex.what() << std::endl; } return didSucceed; }
The compiler, Visual Studio 2015 Update 1, spat out the error pointing to the line above marked with
the comment COMPILER ERROR HERE
:
error C2664: 'void boost::log::v2s_mt_nt6::sinks::basic_text_ostream_backend<char>::add_stream(const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>> &)': cannot convert argument 1 from 'boost::shared_ptr<T>' to 'const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>> &' with [ T=std::ofstream ] note: Reason: cannot convert from 'boost::shared_ptr<T>' to 'const boost::shared_ptr<std::basic_ostream<char,std::char_traits<char>>>' with [ T=std::ofstream ] note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
I was flummoxed. std::ofstream
is a typedef for std::basic_ofstream
, which is a class derived from basic_ostream
. So, there should be no reason why the error should be thrown. The conversion from non-const to const shouldn't be an issue either, though it's a red herring and would cause most folks to look at that first and maybe add a const qualifier. Adding the const qualifier won't make a jot of difference.
I looked up the definition of std::ofstream
and sure enough, it looked like this:
typedef basic_ofstream<char, char_traits<char> > ofstream;
and a little further up in the same file is a forward declaration for basic_ofstream, which looks like this:
template<class _Elem, class _Traits = char_traits<_Elem> > class basic_ofstream;
The one odd thing was that Intellisense found this in the iosfwd
header file and was a forward declaration. But I wanted the definition, which Intellisense stubbornly refused to show me. So, I tried finding all references which only yielded the earlier mentioned ofstream definition basic_ostream forward declaration. Even more odd.
The standard says std::ofstream
is defined in the fstream
header. So, I cracked that open to find the definition of std::basic_ofstream
, which was just as I had expected:
template<class _Elem, class _Traits> class basic_ofstream : public basic_ostream<_Elem, _Traits> { // output stream associated with a C stream ... ... ... };
Now, I didn't #include <iosfwd>
explicitly anywhere. It was getting included by one of the standard C++ headers or one of the boost headers. MSDN says, of iosfwd:
Declares forward references to several template classes used throughout iostreams. All such template classes are defined in other standard headers. You include this header explicitly only when you need one of its declarations, but not its definition.
I figured, more along the lines of "Let me throw this at the wall and see if it sticks.", that I should include the fstream header explicitly. In hindsight, seeing that Intellisense could only show me the forward declaration, it should've been the first thing to try.
So, I explicitly added a #include <fstream>
to my code. Voilà! The error disappeared and the code compiled just fine.
It's a tad irritating (Who am I kidding? It's outright frustrating.) when the compiler throws out an error like the one above, but the definitions and declarations seem to be present - yeah, in this case a forward declaration only, but I would expect the compiler to complain that it didn't have enough information (in this case the definition) to determine whether ofstream
/ basic_ofstream
was derived from basic_ostream
.
Lesson learned: Explicitly include headers that contain the definitions, do not rely on indirect inclusions.