【问题标题】:Converting member function pointers in templates转换模板中的成员函数指针
【发布时间】:2018-09-13 13:41:58
【问题描述】:

假设我有以下两个类:

template<typename T>
struct Base
{
    void foo();
};

struct Derived : Base<Derived> {};

我可以这样做:

void (Derived::*thing)() = &Derived::foo; 

编译器很高兴(正如我所料)。

当我把它放在两个级别的模板中时,它突然爆炸了:

template<typename T, T thing>
struct bar {};

template<typename T>
void foo()
{
    bar<void (T::*)(),&T::foo>{};
}

int main()
{
    foo<Derived>();  // ERROR
    foo<Base<Derived>>(); // Works fine
}

这失败了:

non-type template argument of type 'void (Base<Derived>::*)()' cannot be converted to a value of type 'void (Derived::*)()'

godbolt

为什么简单的案例成功而复杂的案例失败了?我相信这与this 问题有关,但我不完全确定......

【问题讨论】:

    标签: c++ templates language-lawyer member-function-pointers


    【解决方案1】:

    @YSC 确定了&amp;Derived::foo; 的类型。既然您想知道为什么要进行这种隐式转换...

    void (Derived::*thing)() = &Derived::foo; 
    

    ...正常飞行但不在模板中,原因如下:

    [temp.arg.nontype]

    2 非类型模板参数的模板参数应为 模板参数类型的转换常量表达式。

    [expr.const]

    4 T 类型的转换后的常量表达式是一个表达式, 隐式转换为类型 T,其中转换后的表达式是 常量表达式和隐式转换序列只包含

    • [...]

    我省略的列表不包含pointer to member conversions。从而使该模板参数对您指定的参数无效。


    一个简单的解决方法是使用decltype(&amp;T::foo) 而不是void (T::*)() 作为类型参数。这是一个格式良好的替换:

    bar<decltype(&T::foo), &T::foo>{};
    

    是否可以接受,当然取决于你的用例,超出了 MCVE 的范围。

    【讨论】:

      【解决方案2】:

      那是因为&amp;Derived::foo 实际上是void (Base&lt;Derived&gt;::*)() 类型:

      [expr.unary]/3

      一元 & 运算符的结果是指向其操作数的指针。操作数应为左值或限定 ID。如果操作数是一个限定id,命名某个类C的非静态或变体成员m,类型为T,则结果类型为“指向类型T的类C成员的指针”,并且是一个prvalue,指定C​::​米。

      注意“某个类型为 T 的 C 类的成员 m”...可怕的措辞。

      【讨论】:

      • 你怎么形容它更好?
      • @Barry 我希望没有人会质疑它,该死!这是一个非建设性的批评。你不应该做出这样的批评,我不应该做出这样的批评,没有人应该。然而每个人有时都会这样做^^
      • 我的意思是,措辞不像...用花岗岩雕刻而成。如果你能想出更好的措辞,它是可以改变的。
      • @Barry 令我不安的是,[axpr.unary]/3 对我来说很重要的所有情况都是在通过其(直接)子类形成指向类成员的指针时。但是“某种阶级的”这个措辞并不引人注目。以 OP 为例,当我写 &amp;Derived::foo 时,我会将“某个类”解释为 Derived。
      【解决方案3】:

      请注意,即使没有模板,这也会是一个错误,例如,如果您的类不是模板:

      struct Base {
          void foo();
      };
      
      struct Derived : Base {};
      

      你还是不行foo&lt;Base&gt;():https://ideone.com/MQE0ff


      一种可能的替代解决方案首先是为了简化,而不是采用 2 个模板参数,我将让 bar 使用 auto 模板参数类型:

      template<auto thing>
      struct bar{};
      

      接下来我们需要实现is_specialization_of:

      template<template<typename...> class T, typename U>
      struct is_specialization_of : std::false_type {};
      
      template<template<typename...> class T, typename... Ts> 
      struct is_specialization_of<T, T<Ts...>> : std::true_type {};
      

      现在我们可以重写foo 以使用is_specialization_of,我们可以确定我们是否通过了Base 特化或另一个类(我们假设它派生自Base 特化。)

      template<typename T>
      void foo()
      {
          conditional_t<is_specialization_of<Base, T>::value, bar<&T::foo>, bar<&Base<T>::foo>>{};
      }
      

      我已经稍微扩展了您的示例,以便在 bar 中实际调用 thing,并在其他方面纳入了我的建议。你可以在这里查看:https://coliru.stacked-crooked.com/a/2a33b8bd38896ff5

      【讨论】:

      • 这看起来像是一条评论 ;)
      • @YSC 是的 :( 我在另一个窗口中输入示例时不小心提交了...然后结果我花了更长的时间才能完成。(我还在学习很多模板的东西,所以我有点慢。)
      猜你喜欢
      • 1970-01-01
      • 2020-12-08
      • 2015-02-25
      • 2012-07-05
      • 1970-01-01
      • 2010-09-13
      • 2020-09-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多