Issue
I have some code that works well in gcc10 (debian bullseye):
#include <vector>
#include <filesystem>
#include <slib/logmacros.h> // template <class T> void LogVerbose(T content);
using namespace slib;
using Paths=std::vector<std::filesystem::path>;
inline std::ostream &operator << (std::ostream &p_stream, const Paths &p_paths)
{
std::string sep;
for(const auto &p: p_paths)
{
p_stream << sep << p;
sep = ",\n";
}
return p_stream;
}
namespace internal {
std::string BusinessLogic(Paths& p_paths)
{
return slib::stringutils::Format("Paths are: %1", p_paths);
}
} // namespace
In GCC10, this worked great, but in GCC12 (debian bookworm), I get the following error:
error: no match for ‘operator<<’ (operand types are ‘std::stringstream’ {aka ‘std::__cxx11::basic_stringstream’} and ‘const std::vectorstd::filesystem::__cxx11::path’
Format()
is a function that will ultimately should call that operator. This code below isn't anything I have control over, but can raise bugs if it's something in there.
namespace slib {
namespace stringutils {
template <class TData>
void DoParameterReplace(std::stringstream& p_stream, std::string& p_format, unsigned p_index, const TData& p_to, std::string::size_type &p_from_pos)
{
p_stream.str(""); // makes result empty but keeps manipulators
p_stream << p_to;
// manipulators are functions
DoParameterReplace(p_format, p_index, p_stream.str(), p_from_pos, std::is_function<TData>::value);
}
template <typename ... TParams>
std::string Format( std::string p_format, TParams && ... p_params )
{
std::stringstream ss;
std::string::size_type from_pos = 0u;
unsigned index = 0;
// C++17 comma fold
((DoParameterReplace(ss, p_format, index++, p_params, from_pos)),...);
(void)index; // otherwise gives I think incorrect warning error: variable ‘index’ set but not used [-Werror=unused-but-set-variable]
(void)from_pos; // same
return p_format;
}
} // namespace
} // namespace
I'm not sure why gcc now fails to find the operator overload.
I tried:
- changing
ostream& operator<<(ostream& , ... )
tostringstream& operator<<(stringstream& ...)
- Placing my
operator<<
innamespace slib { namespace stringutils {}}
- Placing my
operator<<
innamespace internal {}
Solution
The old GCC behavior was a long-standing bug that has been fixed with GCC 12, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51577.
The lookup for a dependent operator should only consider overloads that are visible at the point of definition via usual unqualified lookup and via ADL at the point of instantiation. However GCC, non-conforming to the standard, also did usual unqualified lookup from the point of instantiation.
Your code seems to rely on the fact that this bug is present, because your operator<<
overload is not declared in a namespace scope in which it could be found by ADL.
You can't reliably overload an operator for types if these types do not depend on one of your own types. In particular you can't add a stream output for std::vector<std::fileysystem::path>
, which contains no type that belongs to you. (In particular adding the overload to the std
namespace, which would give the intended ADL behavior according to the core language, is not permitted by the standard library rules.)
You could derive a type from either std::vector<std::fileysystem::path>
or just std::fileysystem::path
(whether by inheritance or composition) and then declare the overload in the same namespace scope as that type for it. Then the overload can be found via ADL if the argument is your derived type (or a std::vector
of your derived type).
The old GCC behavior is somewhat dangerous. It makes it much easier to accidentally violate ODR because now the some extra operator<<
declaration in one translation unit may cause a template specialization instantiation in that translation unit to use that overload, while an instantiation of the same specialization in another translation unit may use a different one. The lookup rules prevent such mistakes as long as the operator overloads are always declared together with the type that they overload for in the same namespace and header file.
Answered By - user17732522 Answer Checked By - Marie Seifert (WPSolving Admin)