Home > database >  Why C pretends to forget previously parsed templates?
Why C pretends to forget previously parsed templates?

Time:03-27

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 > ).

CodePudding user response:

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

CodePudding user response:

As the other answer stated, you could declare all your functions first before define any of them. However, this answer is about OP's another question

Is there a way to somehow force compiler to respect all the declarations including future ones?

Normally, you could use some Customisation Point technique to achieve your goal, such as C 20 CPO (niebloids), and plain old ADL.

But in your particular example, all types are the STL library types and inside namespace std. Thus you cannot define your functions inside the std to make them part of ADL. Thus C 20 CPO and plain ADL won't work.

There are several approaches which can make your example work.

tag_invoke

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

namespace lib{
    inline constexpr struct write_fn{
        // skip noexcept and requires for simplicity
        decltype(auto) operator()(std::fstream& f, const auto& t) const {
            return tag_invoke(*this, f, t);
        }
    } write{};

    void tag_invoke(write_fn, std::fstream & f, const integral auto & data){
        f.write( (const char*)&data, sizeof( data ) );
    }

    void tag_invoke(write_fn, std::fstream & f, const pair<auto,auto> & p ) {
        lib::write( f, p.first );
        lib::write( f, p.second );
    }

    void tag_invoke(write_fn, std::fstream & f, const std::ranges::range auto& data) {
        const uint64_t size = std::ranges::size( data );
        lib::write(f, size );
        for (const auto& i : data){
            lib::write( f, i );
        }
    }

}

int main() {
    auto f = std::fstream( "spagetti", std::ios::out | std::ios::binary );
    
    lib::write(f, std::pair< int, int >( 123, 777 ) );
    lib::write(f, std::map< int, int >{ { 1, 99 }, { 2, 98 } } );
    lib::write(f, std::pair<std::map< int, int >, int >( { { 1, 99 }, { 2, 98 } }, 777 ) );
}

godbolt link

This works also because of ADL. as the tag write_fn is inside lib namespace

Class Template Specialisation

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

namespace lib{

    template <class T>
    struct write_impl;

    inline constexpr struct write_fn{
        // skip noexcept and requires for simplicity
        decltype(auto) operator()(std::fstream& f, const auto& t) const {
            return write_impl<std::decay_t<decltype(t)>>::apply(f,t);
        }
    } write{};

    template <integral T>
    struct write_impl<T>{
        static void apply(std::fstream & f, const T& data){
            f.write( (const char*)&data, sizeof( data ) );
        }
    };

    template <class T, class U>
    struct write_impl<std::pair<T, U>>{
        static void apply(std::fstream & f, const std::pair<T, U>& p){
            lib::write( f, p.first );
            lib::write( f, p.second );
        }
    };

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

}

int main() {
    auto f = std::fstream( "spagetti", std::ios::out | std::ios::binary );
    
    lib::write(f, std::pair< int, int >( 123, 777 ) );
    lib::write(f, std::map< int, int >{ { 1, 99 }, { 2, 98 } } );
    lib::write(f, std::pair<std::map< int, int >, int >( { { 1, 99 }, { 2, 98 } }, 777 ) );
}

[godbolt link] (https://godbolt.org/z/n7Gbosscn)

Language support

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2547r0.pdf

However, I don't think the authors have implemented a compiler that supports it

  • Related