Thursday, April 14, 2022

[SOLVED] Why C++ pretends to forget previously parsed templates?

Issue

Here is data serialization program which compiles with gcc 11.1:

#include <fstream>
#include <ranges>
#include <concepts>
#include <map>
using namespace std;

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 );
}

template< ranges::range T >
void write( fstream & f, const T & data ) {
    const uint64_t size = ranges::size( data );
    write( f, size );
    for ( const auto & i : data )
        write( f, i );
}

int main() {
    auto f = fstream( "spagetti", ios::out | ios::binary );
    const bool pls_compile = true;
    if constexpr (pls_compile) {
        write( f, pair< int, int >( 123, 777 ) );
        write( f, map< int, int >{ { 1, 99 }, { 2, 98 } } );
    }
    else
        write( f, pair< map< int, int >, int >( { { 1, 99 }, { 2, 98 } }, 777 ) );
}

which made me a happy developer of serialization library which provides functions to serialize to file anything that is integral || pair || range. But it turned out that if you set pls_compile = false, then compilation fails with spagetti.cpp:12:10: error: no matching function for call to 'write(std::fstream&, const std::map<int, int>&)'. It can't be fixed by moving declaration of write( pair ) past write( range ) because pls_compile = true will stop compiling then.

What is the best way to fix it and why compiler forgets about existence of write( range ) while generating implementation of write( pair< map, int > ) based on write( pair ) template? It clearly should be already familiar with write( range ) since it already proceeded down to write( pair< map, int > ).


Solution

In order for a function to participate in overload resolution, it must be declared before the point of the use in the compilation unit. If it is not declared until after the point of use, it will not be a possible overload, even if the compile has proceeded past the point of definition (eg, if the use is in a instatiation of a template defined before the declation but triggered by a use of a template afterwards, as you have here.)

The easiest fix is to just forward declare your templates/functions. You can't go wrong forward declaring everything at the top of the file:

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


Answered By - Chris Dodd
Answer Checked By - David Marino (WPSolving Volunteer)