成员函数指针值上的 Consexpr - 未定义的行为?

Posted

技术标签:

【中文标题】成员函数指针值上的 Consexpr - 未定义的行为?【英文标题】:Constexpr on member function pointer value - undefined behavior? 【发布时间】:2019-06-02 12:34:50 【问题描述】:

我对 C++ 中的简单反射机制有疑问。我想要一种模板类型,它应该以不同的成员函数指针作为模板参数表现不同:

[解决方案#1,按标准来说是不好的]

如果我有一个具有类类型及其成员函数指针的类模板,我不能专门化成员指针为空,因为我不能专门化“具有依赖类型的非类型模板参数”(请参阅​​:https://en.cppreference.com/w/cpp/language/partial_specialization参数列表 [5])

template<class O, void(O::*foo)() = nullptr>
struct p
;

template<class O, void(O::*foo)()>
struct p<O, nullptr>
;

[解决方案#2,GCC 问题]

如果我尝试专门研究一个推断的 constexpr 值,该值反映到成员指针,编译器会遇到不同的问题。 GCC8.2 x64 告诉我 nullptr == foo 表达式在上下文中不是常量:p&lt;A, &amp;A::f&gt; j;。但是 ARM GCC8 说没关系。我认为这是一些内存布局问题,struct A 在模板引擎尝试评估具体函数指针时并不完整。

template<class O, void(O::*foo)() = nullptr, bool = nullptr == foo>
struct p
;

template<class O, void(O::*foo)()>
struct p<O, foo, true>
;

struct A

    void f();
    p<A, &A::f> j;
;

[解决方案#2 用法,MSVC 问题]

奇怪的是,MSVC 19.5 x86 上面的没问题,它还有另一个问题。它正在工作,而结构 A 是独立的,或者它继承自一个结构。但是当有两个基本结构(X,Y)时,它会因内部编译器错误而死。

template<class O, void(O::*foo)() = nullptr, bool = nullptr == foo>
struct p
;

template<class O, void(O::*foo)()>
struct p<O, foo, true>
;

struct X;
struct Y;

struct A : X, Y

    void f();
    p<A> i;
    p<A, &A::f> j;
;

Clang7 可以很好地处理上述所有内容。

如果这是一种未定义的行为,谁能解释一下,或者标准中是否有关于这个主题的任何指南?

【问题讨论】:

请注意,与 C++17 相比,您可能拥有template &lt;auto foo = nullptr&gt;,这可能会简化您的专业化和使用。 请不要给你的班级打电话O!你确定能看出问题吗? 您可能想要为 ICE 发布错误报告。 【参考方案1】:

这是CWG 2127。基本上,我们在[temp.class.spec]中有这个限制:

对应于专门化的非类型实参的模板形参的类型不应依赖于专门化的参数。

这基本上意味着我们不能直接做你想做的那种nullptr 专业化。这是...蹩脚的。但是编程中的任何问题都可以通过添加另一层间接来解决吗?

template<class O, void(O::*foo)(), bool is_null>
struct p_impl  /* non-null case */ ;

template<class O, void(O::*foo)()>
struct p_impl<O, foo, true>  /* null case */ ;

template<class O, void(O::*foo)() = nullptr>
using p = p_impl<O, foo, foo == nullptr>;

另一方面,gcc 在比较指向不完整类型成员的指针时似乎存在问题。它拒绝这个:

template <bool> struct Z  ;

struct C

    void f();
    Z<&C::f == nullptr> z; // not a constant expression
;

我想这是gcc bug 56428。

【讨论】:

您好,感谢您的回答。我刚刚编辑了这个问题,更容易被引用。 解决方案#2 gcc 问题也出在您的身上,当您开始将其用作使用其类型为“O”的类的成员时:请参阅:cpp.godbolt.org/z/DpSnwD 也存在 msvc 的问题,当您在模板中使用默认的 nullptr 并尝试从多个基类继承时:一个基类可以:cpp.godbolt.org/z/1W-o3r 两个不行:cpp.godbolt.org/z/EFHbti 我的问题仍然是它是否定义了行为,或者是这是一个愚蠢的问题,因为编译器可以决定如何处理成员函数指针模板参数。【参考方案2】:

这里的问题是您将函数指针(不是常量,因为它可能仅在链接时才知道)与nullptr(即)进行比较。

你可以通过std::integral_constant为这种函数类型生成相对常量,然后进行比较。

所以类似(对于基本的C++11)是:

template<class O>
using F = void(O::*)();

template<class O>
using NullF = std::integral_constant<F<O>, nullptr>;

template<class O, F<O> foo = nullptr,
    bool = std::is_same<std::integral_constant<F<O>, foo>, NullF<O>>::value>
struct p
;

template<class O, F<O> foo>
struct p<O, foo, true>
;

struct A

    void f();
    p<A, &A::f> j;
    p<A> k;
;

使用 C++17,您也许可以只使用带有if constexpr 检查的函数:

template<class O>
using F = void(O::*)();

template<class O>
using NullF = std::integral_constant<F<O>, nullptr>;

template<class O, F<O> foo = nullptr>
struct p

    p()
    
        if constexpr (std::is_same_v<std::integral_constant<F<O>, foo>, NullF<O>>)
        
            // yay
        
    
;

struct A

    void f();
    p<A, &A::f> j;
    p<A> i;
;

【讨论】:

以上是关于成员函数指针值上的 Consexpr - 未定义的行为?的主要内容,如果未能解决你的问题,请参考以下文章

在 [重复] 中调用布尔值上的成员函数 execute()

为啥在已删除指针上调用非虚拟成员函数是未定义的行为?

未定义符号,使用指针 C++ 访问成员函数

指向匿名联合成员的指针是不是相等?

Consexpr 替代放置 new 以使内存中的对象未初始化?

将成员函数传递给函数指针