为什么 C++ 假装忘记以前解析过的模板?

Sla*_*lav 1 c++ gcc templates c++20

这是使用以下命令编译的数据序列化程序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 ) );
}
Run Code Online (Sandbox Code Playgroud)

这使我成为序列化库的快乐开发人员,该库提供序列化以归档任何integral || pair || range. 但事实证明,如果你设置了pls_compile = false,那么编译就会失败并显示spagetti.cpp:12:10: error: no matching function for call to 'write(std::fstream&, const std::map<int, int>&)'. 它不能通过移动write( pair )过去的声明来修复write( range ),因为pls_compile = true届时将停止编译。

修复它的最佳方法是什么?为什么编译器在生成基于模板的实现时会忘记的存在?显然应该已经熟悉了,因为它已经进行到了。write( range )write( pair< map, int > )write( pair )write( range )write( pair< map, int > )

Chr*_*odd 6

为了使函数参与重载决策,必须在编译单元中的使用点之前声明该函数。如果直到使用点之后才声明它,则即使编译已经进行到定义点之后才声明它也不会是可能的重载(例如,如果使用是在声明之前定义但触发的模板的实例化)之后使用模板,就像您在这里一样。)

最简单的解决方法是转发声明您的模板/函数。在文件顶部声明所有内容是不会出错的:

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 );
Run Code Online (Sandbox Code Playgroud)