【问题标题】:template deduction: Why the function pointer templates definitions are not matching when they are const and/or references?模板推导:为什么函数指针模板定义在 const 和/或引用时不匹配?
【发布时间】:2020-01-29 20:41:25
【问题描述】:

Is possible to fix the iostream cout/cerr member function pointers being printed as 1 or true? 问题之后,我正在尝试编写一种与 C++ 98 兼容的方式来打印任何函数指针。

为此,我使用了一个伪造的 C++“可变参数”模板,即,将所有函数定义写入最多 n 个参数。但是,我的假可变参数仅适用于具有 0 个参数的函数指针,如下例所示:https://godbolt.org/z/x4TVHS

#include<iostream>

template<typename Return>
std::ostream& operator <<(std::ostream& os, Return(*pointer)() ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0 ) ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0, typename T1>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0, const T1& t1 ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 1
//    3. funptr 1

如果我使用 C++11 真正的可变参数模板编写等效版本,那么一切正常:https://godbolt.org/z/s6wdgp

#include<iostream>

template<typename Return, typename... Args>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( Args... ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 0x100401087
//    3. funptr 0x100401093

分析代码后,我注意到与 then 的唯一区别是 C++11 示例中的类型不是 const 引用。然后,我从 C++98 中删除了常量和引用,它开始工作:https://godbolt.org/z/ZrF66b

#include<iostream>

template<typename Return>
std::ostream& operator <<(std::ostream& os, Return(*pointer)() ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( T0 ) ) {
    return os << (void*) pointer;
}

template<typename Return, typename T0, typename T1>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( T0, T1 ) ) {
    return os << (void*) pointer;
}

void fun_void_void(){};
void fun_void_double(double d){};
double fun_double_double(double d){return d;}

int main() {
    std::cout << "1. " << fun_void_void << std::endl;
    std::cout << "2. " << fun_void_double << std::endl;
    std::cout << "3. " << fun_double_double << std::endl;
}

// Prints:
//    1. funptr 0x100401080
//    2. funptr 0x100401087
//    3. funptr 0x100401093

为什么函数指针模板定义在 const 和/或引用时不匹配?

【问题讨论】:

  • 因为它们是不同的类型?不知道你为什么对 double(*)(double) 不匹配 double(*)(const double&amp;) 感到困惑
  • 您能否解释一下为什么这种行为不是您所期望的?似乎很明显有例如没有类型ReturnT0,这样Return(*)(const T0&amp;) 就是double(*)(double)
  • 我不会想到这种行为,因为当我构建模板函数时,描述其参数的最通用方式是const Type&amp; arg(在新版本的 C++ 中,我可以使用完美转发和 @ 987654335@)。虽然在这里使它工作的唯一方法是使用Type arg。只需使用此签名,它就可以接受任何其他函数指针类型为const Type arg(与参数传递相反)。
  • @user 也许你对模板的工作方式有误解。模板参数将替换为某种类型(显式给出或以某种方式推导),然后调用该函数,就好像它是具有替换参数的非模板函数一样。如果您要为函数调用 operator&lt;&lt;(std::cout, fun_double_double) 显式提供模板参数,您会为 T0 提供什么类型,您认为这会导致函数调用格式正确?
  • 顺便说一句。 Type arg 作为函数参数比const Type&amp; argType&amp;&amp; arg 更通用。您可以显式地为Type 提供任何类型,无论是否引用,因此使用Type arg 获取任何参数类型,但使用const Type&amp; arg,您只能将const 引用作为参数类型,即使您尝试明确指定类型。只是 Type arg 的引用类型永远不会自动推导出来,而且它更经常将参数作为泛型代码中的引用,特别是如果您不知道所涉及的类型是否可以复制/移动。

标签: c++ c++11 pointers templates c++98


【解决方案1】:
template<typename Return, typename T0>
std::ostream& operator <<(std::ostream& os, Return(*pointer)( const T0& t0 ) ) {
  return os << (void*) pointer;
}
void f(int) {}
os << f;

这不起作用,因为没有类型 ReturnT0 使得 Return(*)(const T&amp;) 匹配 void(*)(int) 完全正确

顺便说一句,重写不在关联命名空间中的运算符是不好的,因为它非常脆弱。并且您不能覆盖std 中的运算符。所以这个计划很糟糕。

【讨论】:

  • 当您说 you aren't allowed to override operators in std 时,您的意思是 std::ostream&amp; operator &lt;&lt;(...)?是否有参考解释为什么我不允许这样做?
  • @user 你不能在namespace std 中放任何东西,除了一些模板特化。我的意思是namespace std { std::ostream&amp; operator&lt;&lt;( std::ostream&amp; os, void(*)(int) ); } 使您的程序格式错误,无需诊断。再加上您应该始终在所涉及类型之一的关联命名空间中重载运算符的规则意味着......您不应该这样做。
  • @user namespace A { struct Foo {}; std::ostream&amp; operator&lt;&lt;( std::ostream&amp; os, Foo const&amp; ) { return os&lt;&lt;"FOO!"; }namespace A 内找不到您定义的&lt;&lt;,因为一旦找到任何operator&lt;&lt;,非ADL 查找规则就会停止,即使是那些不适用的到您有问题的类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-22
  • 2015-10-17
  • 2021-06-22
  • 1970-01-01
相关资源
最近更新 更多