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

Why C++ pretends to forget previously parsed templates?

这里是用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 ) );
}

这让我成为序列化 的快乐开发者,它提供了序列化以归档任何 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 将停止编译。

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

函数要参与重载决策,必须在编译单元use点之前声明 .如果它直到使用点之后才被声明,它就不会是一个可能的重载,即使编译已经超过了定义点(例如,如果使用是在声明之前定义的模板的实例化但触发之后使用模板,就像您在此处所做的那样。)

最简单的解决方法是转发声明您的 templates/functions。在文件顶部声明 everything 不会出错:

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

正如另一个答案所述,您可以在定义任何函数之前先声明所有函数。然而,这个答案是关于OP的另一个问题

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

通常,您可以使用一些自定义点技术来实现您的目标,例如 C++20 CPO (niebloids) 和普通的旧 ADL。

但在您的特定示例中,所有类型都是 STL 库类型并且在 namespace std 内部。因此,您不能在 std 中定义函数以使其成为 ADL 的一部分。因此 C++20 CPO 和普通 ADL 将不起作用。

有几种方法可以让您的示例发挥作用。

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

这也是因为 ADL。因为标签 write_fnlib 命名空间

Class 模板专业化

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

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

语言支持

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

但是,我认为作者没有实现支持它的编译器