C++ 将指向base成员的指针转换为指向派生
以下是一个简化示例: 结构B{void f();}; 结构D:B{}; constexpr auto as_d=static_cast(&d::f);//(1) 模板 结构X{}; X;//(2) gcc、clang和MSVC都接受标记为C++ 将指向base成员的指针转换为指向派生,c++,language-lawyer,c++17,C++,Language Lawyer,C++17,以下是一个简化示例: 结构B{void f();}; 结构D:B{}; constexpr auto as_d=static_cast(&d::f);//(1) 模板 结构X{}; X;//(2) gcc、clang和MSVC都接受标记为(1)的声明。gcc和clang均拒绝声明标记为(2),但MSVC接受该声明 gcc和clang的错误消息都表明它们知道as\u d是指向B成员的指针。叮当声: :9:3:错误:很抱歉,尚未支持指向成员类型void(D::*)()的指针的非类型模板参数,该参数
(1)
的声明。gcc和clang均拒绝声明标记为(2)
,但MSVC接受该声明
gcc和clang的错误消息都表明它们知道as\u d
是指向B
成员的指针。叮当声:
:9:3
:错误:很抱歉,尚未支持指向成员类型void(D::*)()
的指针的非类型模板参数,该参数引用不同类的成员B::f
通用条款:
:9:7
:错误:void(D::*)(){((void(D::*)())B::f),0}
不是类型void(D::*)()的有效模板参数。
谁是对的?如果是gcc/clang,我们违反的规则是什么?在我看来,它确实像是,因为d
是一个类型为void(d::*)()
的转换常量表达式 嗯,这最终非常有趣。就语言而言,程序是有效的-,因为d
确实满足作为有效的非类型模板参数的要求(它是正确类型的转换常量表达式)
但是,在这种情况下(即,具有一个非类型模板参数,其类型是指向派生成员的指针,但其值是指向基成员的指针)。因此,针对该ABI(即clang和gcc)的编译器无法接受此代码。这就解释了为什么clang的错误是“对不起,还没有”而不是“不,糟糕!”
另一方面,其他ABI没有这样的损坏问题,因此MSVC和ICC都能够很好地编译程序 “铿锵”错误的措辞表明“这应该是可能的,我们知道它应该,但是,呃,我们无法让它工作”。因为基成员函数的指针转换为成员函数的指针在程序集级别非常棘手。似乎允许这样的转换。它不应该是X
?@matiu否,因为X
的模板参数是一个“非类型模板参数”(一个接受constepr
值的参数)。这不更像是对C++17中引入的缺陷的攻击吗?因为在C++14中,指向成员类型的指针的模板参数必须是&T::name expression
,:非类型、非模板模板参数的模板参数应该是以下参数之一:[……]指向成员的指针,如[expr.unary.op]中所述;[...]. 这一限制在C++17中已被删除,现在我们有了一个不一致的行为,X
格式不正确,但不是X
。我假设指向成员函数类型的指针的非类型模板参数可能会被破坏为一个字符,标识函数是否为虚拟函数,后跟一个名称和非虚拟函数的此偏移量,或虚拟函数的vtable偏移量和此偏移量。虽然这将是一个破坏性的ABI更改,我不知道编译器供应商愿意多久做一次。
struct B { void f(); };
struct D : B { };
constexpr auto as_d = static_cast<void(D::*)()>(&D::f); // (1)
template <void (D::*)()>
struct X { };
X<as_d> x; // (2)