C++ GCC C&x2B+;14/17成员函数指针模板参数的差异
我有一些示例代码,在GCC/Clang/MSVC上用C++14进行编译,在Clang/MSVC上用C++17进行编译,但在GCC 8.x到10.1上用C++17进行编译时会产生一个错误C++ GCC C&x2B+;14/17成员函数指针模板参数的差异,c++,gcc,c++17,overloading,function-templates,C++,Gcc,C++17,Overloading,Function Templates,我有一些示例代码,在GCC/Clang/MSVC上用C++14进行编译,在Clang/MSVC上用C++17进行编译,但在GCC 8.x到10.1上用C++17进行编译时会产生一个错误 #include <vector> // vector template< typename Seq, typename Seq::value_type& ( Seq::*next )(), void ( Seq::*pop )() > v
#include <vector> // vector
template< typename Seq,
typename Seq::value_type& ( Seq::*next )(),
void ( Seq::*pop )() >
void f( Seq& );
template< typename Seq >
void g( Seq& seq )
{
f< Seq, &Seq::back, &Seq::pop_back >( seq );
}
void foo()
{
std::vector< int > v;
g( v );
}
我知道第二个参数,&Seq::back
是一个重载函数;我创建了一个非常量重载的中间成员函数指针,并将其作为第二个参数传递给调用f
,但我收到了几乎相同的错误
所以,基本问题是,这是无效的C++17代码,还是一个GCC错误?假设它是无效的C++17,我如何使它有效
额外问题:什么是'int&(std::vector::*)(){((int&(std::vector::*)())std::vector::back),0}'
?我对{
/}
对和0
感到非常惊讶。我知道外部部分是方法签名,而内部第一部分是将重载方法强制转换为预期签名,但是为什么是{
/}
对和0
?初始化列表?结构?成员指针的内部结构?
。。。[T]如果一个C++程序的行为是不明确的(可能是不正确的)…形成指向指定标准库非静态成员函数([member.functions]
)或标准库成员函数模板实例化的成员的指针
这基本上是为了允许实现者向标准函数添加默认模板参数和默认参数以及所有其他东西,这将使它们的函数指针和引用的类型与标准所说的接口不一致。此外,实现可以改变那些本身不太隐藏的部分,您不应该依赖它们。因此全面禁止(可寻址函数的例外情况通常出现在iostream
和iomanip
中)。您基本上应该使用lambdas或类似工具将标准函数包装到其“预期”接口中:
#include <vector>
// or just take functors
template<typename Seq, typename Seq::value_type &(*next)(Seq&), void (*pop)(Seq&)>
void f(Seq&);
template<typename Seq>
void g(Seq &seq) {
constexpr auto back = +[](Seq &s) -> typename Seq::value_type& { return s.back(); };
constexpr auto pop_back = +[](Seq &s) -> void { s.pop_back(); };
f<Seq, back, pop_back>(seq);
}
int main() {
std::vector<int> v;
g(v);
}
我将把不可靠的错误消息标记为“GCC的实现细节正在泄漏”,否则错误中的表达式就没有多大意义。也许GCC在函数指针旁边保留了一些额外的标志或其他东西(我知道,例如指向虚拟
成员函数的指针会导致指向成员函数的指针的表示形式很奇怪)。无论如何,我相信这次拒绝是另一个GCC错误。“指针指向-noexcept
-member function指向指针指向成员函数”在转换后的常量表达式(模板非类型参数是该表达式)中完全有效,将这种转换后的&F::foo
放入constepr
变量中即可显示。然而,出于某种原因,GCC根本不喜欢它。正如您所发现的,修复方法是只编写两个版本的模板:一个用于noexcept
,另一个用于not-noexcept
TL;DR:您的代码已损坏,但GCC可能更坏。在C++17中,“noexcept”现在是类型系统的一部分,“libstdc++”将“noexcept”添加到几个STL API中,在本例中为“back”、“front”和类似的getter。此“noexcept”添加是由明确允许的。此外,显式地允许从“noexcept”向“NonNoExcept”函数指针进行隐式强制转换,因此问题的代码应该仍然有效。GCC未正确隐式转换成员函数指针 因此,在GCC下编译时,我可以显式地提供'noexcept'重载:
#include <vector> // vector
template< typename Seq,
typename Seq::value_type& ( Seq::*peek )(),
void ( Seq::*pop )() >
void f( Seq& );
#if defined( __GNUC__ ) && !defined( __clang__ )
template< typename Seq,
typename Seq::value_type& ( Seq::*peek )() noexcept,
void ( Seq::*pop )() >
void f( Seq& );
#endif // defined( __GNUC__ ) && !defined( __clang__ )
template< typename Seq >
void g( Seq& seq )
{
f< Seq, &Seq::back, &Seq::pop_back >( seq );
}
void foo()
{
std::vector< int > v;
g( v );
}
#包含//向量
模板
无效f(序号&);
#如果定义了(_ugnuc__;)和&!已定义(uuu-clang_uuu)
模板
无效f(序号&);
#endif//defined(_ugnuc___;)和&!已定义(uuu-clang_uuu)
模板
无效g(序号和序号)
{
f(Seq);
}
void foo()
{
std::vectorv;
g(v);
}
允许实现添加
noexcept
(但不允许constexpr
)。此外,从C++20开始,您根本不允许形成指向标准库(成员)函数的指针,如另一个答案中所述。@Davidsherring谢谢,我找到了允许这样做的标准描述,并修改了我的答案。指向noexcept函数的指针向上转换为指向noexcept函数的指针。你不需要像这样超负荷工作。GCC只是有点奇怪。@HTNW啊,这是一个公平的观点。我会更新答案。你的调查给我指明了正确的方向。然而,可寻址函数似乎是C++20的限制。我相信C++17中关于“noexcept”的更改更直接地解释了我的问题@CharlesLWilcox:请记住,C++20规则并不完全是新的:[member.functions]/2在C++17中已经允许了一些技巧,如默认参数,这些技巧会使指向成员函数的指针出错。
struct F { void foo() noexcept; }; // std::vector<int>::back is noexcept in libstdc++
constexpr inline void (F::*ptr)() = &F::foo;
template<void (F::*f)()> void g();
int main() { g<ptr>(); }
// Clang is happy, GCC is not
#include <vector> // vector
template< typename Seq,
typename Seq::value_type& ( Seq::*peek )(),
void ( Seq::*pop )() >
void f( Seq& );
#if defined( __GNUC__ ) && !defined( __clang__ )
template< typename Seq,
typename Seq::value_type& ( Seq::*peek )() noexcept,
void ( Seq::*pop )() >
void f( Seq& );
#endif // defined( __GNUC__ ) && !defined( __clang__ )
template< typename Seq >
void g( Seq& seq )
{
f< Seq, &Seq::back, &Seq::pop_back >( seq );
}
void foo()
{
std::vector< int > v;
g( v );
}