为什么 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 ) );
}
这也是因为 ADL。因为标签 write_fn
在 lib
命名空间
内
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
但是,我认为作者没有实现支持它的编译器
这里是用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 ) );
}
这也是因为 ADL。因为标签 write_fn
在 lib
命名空间
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
但是,我认为作者没有实现支持它的编译器