0

I am implementing serialization that's supposed to work by recursive overloading until serializing data breaks down to primitive types. Here it's relevant code at serialization.hpp:

#pragma once
#include <fstream>
#include <ranges>
#include <concepts>
using namespace std;

void write( fstream & f, const integral auto & data );
void write( fstream & f, const pair<auto,auto> & p );
void write( fstream & f, const ranges::range auto & data );

void write( fstream & f, const integral auto & data ) {
    f.write( (const char*)&data, sizeof( data ) );
}
void write( fstream & f, const pair<auto,auto> & p ) {
    write( f, p.first );
    write( f, p.second );
}
void write( fstream & f, const ranges::range auto & data ) {
    const uint64_t size = ranges::size( data );
    write( f, size );
    for ( const auto & i : data ) {
        write( f, i );
    }
}

And it works well for most types providing serialization for free even for std::map, std::list, etc. If user needs to serialize any custom type then it's needed to just provide a void write( fstream & f, const CustomType & t ) { ... } function and it supposed to get serialized even inside any data structure. And it works well until CustomData gets wrapped into a namespace. main.cpp:

#include "serialization.hpp"
#include <fstream>
#include <map>
using namespace std;

struct MyData {
    int custom_data[ 666 ];
};
void write( fstream & f, const MyData & d ) {
    write( f, d.custom_data[ 123 ] );
}

namespace my_namespace {
    struct NamespacedData {
        int custom_data[ 777 ];
    };
}//namespace my_namespace
void write( fstream & f, const my_namespace::NamespacedData & d ) {
    write( f, d.custom_data[ 7 ] );
}

int main() {
    auto f = fstream( "my_data.binary", ios::out | ios::binary );
    f.exceptions( fstream::badbit | fstream::failbit | fstream::eofbit );

    map< int, MyData > my_map;
    //DOES work:
    write( f, my_map );

    map< int, my_namespace::NamespacedData > namespaced_map;
    //does NOT work:
    write( f, namespaced_map );
}

Compilation fails with error: serialization.hpp:16:10: error: no matching function for call to ‘write(std::fstream&, const my_namespace::NamespacedData&)’ ...

How function argument's full name matters here? Isn't it's irrelevant when compiler should try to match any type when looking for fitting overloaded function? Or it's just gcc bug?

Command: g++ main.cpp -std=gnu++20 -fconcepts
g++ version 11.3.0

Slaus
  • 2,086
  • 4
  • 26
  • 41
  • 2
    Put the function *into* the namespace – StoryTeller - Unslander Monica Jan 07 '23 at 16:23
  • 1
    @StoryTeller-UnslanderMonica it worked! But how? Compiler somehow succeeds in finding `void my_namespace::write( fstream & f, const NamespacedData & d )` while failing to find `void write( fstream & f, const my_namespace::NamespacedData & d )`? – Slaus Jan 07 '23 at 16:31
  • 1
    Asked and answered: https://stackoverflow.com/q/8111677/817643 – StoryTeller - Unslander Monica Jan 07 '23 at 16:36
  • @StoryTeller-UnslanderMonica didn't know about `Koenig lookup`, thank you. Still unclear though why compiler couldn't *just* find `write(my_namespace::NamespacedData)`. It looks much more straightforward than finding `my_namespace::write(NamespacedData)`. For me it looks obvious that overloads must be tried to get matched by arguments full names, but I guess nobody suggested it yet. – Slaus Jan 07 '23 at 16:49
  • 1
    Your simpler suggestion basically treats the global namespace as special. Why should things placed there be picked up automatically but not in other namespaces? Even if we did that anyway, as youv'e just demonstrated, folks tend to put stuff in the global namespace willy nilly. That's a huge potential for conflicts in overload resolution. Why ADL? Because if we make any namespace a customization point for templates, it better be one client code easily controls, i.e. their own namespace. – StoryTeller - Unslander Monica Jan 07 '23 at 17:02
  • @StoryTeller-UnslanderMonica thank your for thorough explanation! I meant that compiler was supposed to (intuitively to me) resolve overload based on argument type full name (including any namespaces). And it seems how it actually works, since compiler complains: `no matching function for call to ‘write(std::fstream&, const my_namespace::NamespacedData&)’` - compiler cannot find it, but it's exactly how function was defined. – Slaus Jan 08 '23 at 05:13
  • 1
    It cannot find it because it wasn't declared before the template. Same as C++ always works. There's no looking ahead, except into *specific* declarative regions nominated by ADL. – StoryTeller - Unslander Monica Jan 08 '23 at 08:32
  • @StoryTeller-UnslanderMonica but if I completely remove `my_namespace` compiler then somehow manages to find `write(NamespacedData)` while it's still declared only after `NamespacedData`. – Slaus Jan 08 '23 at 08:53
  • 1
    Because now the **global** namespace is the one where both the type and the function are defined in. It's hardly recommended, though. – StoryTeller - Unslander Monica Jan 08 '23 at 08:53

0 Answers0