I have a simplified analogue of netgen in C++. In one pipeline, I need to output to a file the coordinates of those nodes (as a container std::array of three elements) that fall inside a sphere of a given radius and with a given center. It is required to output lists from 10 to 20 in this set. To implement the solution, I decided to use Ranges from C++20. The problem is that I can't output the filtered nodes to a file. Here is a part of my code
#include "AneuMeshLoader.h"
#include "MeshExceptions.h"
#include "Strukturs.h"
#include <array>
#include <cmath>
#include <fstream>
#include <iostream>
#include <memory>
#include <ranges>
#include <string>
using namespace std;
ostream &operator<<(ostream &os, const array<double, 3> &vec) {
os << "{ ";
bool first = true;
for (const auto &elem : vec) {
os << (first ? "" : ", ") << elem;
first = false;
}
return os << " }";
}
bool isWithinSphere(const Node &node, const array<double, 3> ¢er,
double radius) {
double dx = node.x - center[0];
double dy = node.y - center[1];
double dz = node.z - center[2];
return (dx * dx + dy * dy + dz * dz) <= (radius * radius);
}
int main(int argc, char *argv[]) {
string mesh_file = (argc > 1) ? argv[1] : "MeshExample.aneu";
try {
unique_ptr<MeshLoader> loader = make_unique<AneuMeshLoader>();
Mesh mesh = loader->loadMesh(mesh_file);
array<double, 3> sphere_center = {0.0, 0.0, 0.0};
double radius = 10.0;
std::ofstream output("filtered_nodes.txt");
if (!output.is_open()) {
std::cerr << "Failed to open output file." << std::endl;
return 1;
}
// Pipeline
auto filtered_nodes =
mesh.getNodes() | ranges::views::filter([&](const Node &node) {
return node.isVertex && isWithinSphere(node, sphere_center, radius);
}) |
ranges::views::transform(
[](const Node &node) { return node.coords(); }) |
ranges::views::drop(10) | ranges::views::take(10);
ranges::copy(filtered_nodes.begin(), filtered_nodes.end(),
ostream_iterator<array<double, 3>>(output, " ")); // ERROR!!!!
output.close();
cout << "Filtered nodes have been written to the file." << endl;
Here is the implementation getNodes
from filtered_nodes:
const std::vector<Node> &Mesh::getNodes() const { return nodes; }
Here is the console output when compiling:
In file included from /usr/include/c++/14.2.1/iterator:65,
from /usr/include/c++/14.2.1/ranges:43,
from Main.cpp:9:
/usr/include/c++/14.2.1/bits/stream_iterator.h: In the specification «std::ostream_iterator<_Tp, _CharT, _Traits>& std::ostream_iterator<_Tp, _CharT, _Traits>::operator=(const _Tp&) [с _Tp = std::array<double, 3>; _CharT = char; _Traits = std::char_traits<char>]»:
/usr/include/c++/14.2.1/bits/ranges_algobase.h:287:13: requires from «constexpr std::__conditional_t<_IsMove, std::ranges::in_out_result<_Iter, _Out>, std::ranges::in_out_result<_Iter, _Out> > std::ranges::__copy_or_move(_Iter, _Sent, _Out) [с bool _IsMove = false; _Iter = std::counted_iterator<transform_view<filter_view<ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> >::_Iterator<false> >; _Sent = take_view<drop_view<transform_view<filter_view<ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> > > >::_Sentinel<false>; _Out = std::ostream_iterator<std::array<double, 3> >; std::__conditional_t<_IsMove, in_out_result<_Iter, _Out>, in_out_result<_Iter, _Out> > = in_out_result<std::counted_iterator<transform_view<filter_view<ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> >::_Iterator<false> >, std::ostream_iterator<std::array<double, 3> > >]»
287 | *__result = *__first;
| ~~~~~~~~~~^~~~~~~~~~
/usr/include/c++/14.2.1/bits/ranges_algobase.h:303:38: requires from «constexpr std::ranges::copy_result<_Iter, _Out> std::ranges::__copy_fn::operator()(_Iter, _Sent, _Out) const [с _Iter = std::counted_iterator<std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> >::_Iterator<false> >; _Sent = std::ranges::take_view<std::ranges::drop_view<std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> > > >::_Sentinel<false>; _Out = std::ostream_iterator<std::array<double, 3> >; std::ranges::copy_result<_Iter, _Out> = std::ranges::in_out_result<std::counted_iterator<std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<const std::vector<Node> >, main(int, char**)::<lambda(const Node&)> >, main(int, char**)::<lambda(const Node&)> >::_Iterator<false> >, std::ostream_iterator<std::array<double, 3> > >]»
303 | return ranges::__copy_or_move<false>(std::move(__first),
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~
304 | std::move(__last),
| ~~~~~~~~~~~~~~~~~~
305 | std::move(__result));
| ~~~~~~~~~~~~~~~~~~~~
Main.cpp:56:17: required from here
56 | ranges::copy(filtered_nodes.begin(), filtered_nodes.end(),
| ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57 | ostream_iterator<array<double, 3>>(output, " "));
This is one of those places where namespaces aren't entirely intuitive.
By putting using namespace std;
near the top of your file, you've made everything in the std
namespace visible in the current scope. But you haven't made everything in the current scope visible in the std
namespace.
When you call:
ranges::copy(filtered_nodes.begin(), filtered_nodes.end(),
ostream_iterator<array<double, 3>>(output, " "));
That would find your operator<<
if it were in the std
namespace, but not in the current file's namespace scope where it actually is.
There are a couple of ways to fix this. The crufty, cheating, undefined-behavior way is to put your operator<<
into the std
namespace:
namespace std {
ostream &operator<<(ostream &os, const array<double, 3> &vec) {
os << "{ ";
bool first = true;
for (const auto &elem : vec) {
os << (first ? "" : ", ") << elem;
first = false;
}
return os << " }";
}
}
The other (that's much cleaner, at least in my opinion) is to define a type of your own (e.g., Point
) and define operator<<
in the same namespace where you've defined your type, so the operator can be found via argument dependent lookup.
My personal preference is to define the operator as a friend
inside the body of the associated struct, which helps minimize pollution of the surrounding namespace, because even though the operator is defined there, it's not declared there, so it can basically only be found via ADL from that type.
struct Point {
double x, y, z;
friend ostream &operator<<(std::ostream &os, Point const &p) {
// ...
}
}