1

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> &center, 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, " ")); 
4
  • 1
    Side note : don't use std::array<double,3> when you mean a 3d vector. Make a struct for that. It will make your code 1) more readable, 2) semantically more clear (you prevent your overloads triggering for arrays with 3 values that are not vectors) Commented Nov 10, 2024 at 18:50
  • Your operator<< is not found by name lookup. The lookup is performed from inside namespace std (namely, from the implementation of std::ostream_iterator), and there are plenty of overloads of operator<< there, so ordinary lookup finds them and stops looking; the global namespace is never searched. Nor is your overload found by argument-dependent lookup, since all its parameters are also in namespace std. Commented Nov 10, 2024 at 20:15
  • An overloaded operator should generally be in the same namespace as at least one of its operands. But it's not allowed to put overloads into namespace std Commented Nov 10, 2024 at 20:19
  • std::array doesn't have iostream support. You need to use views::join to flatten your view; then iostream_iterator<double> is available. ranges::copy doesn't need iterator pair; it's better to pass the view itself. If you need formatted output, you can use a range based for to insert EOL(\n), or use std::format_to(iff C++23 std::range_formatter is available). Commented Nov 10, 2024 at 20:29

1 Answer 1

4

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) { // ... } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.