C++指向派生类成员的指针

4

我不理解这里发生了什么。我有一个包含字段a的类Base。现在我创建了一个扩展了Base的Derived类。我还有一个模板函数,通过传递指向成员指针的模板参数来打印我们想要的字段。

#include <iostream>

class Base {
public:
  Base(double a):a{a} {}
  double a;
};

class Derived : public Base {
public:
  Derived(double a) : Base(a) {}
};

template<double (Derived::*p)>
void print(Derived d) {
  std::cout << d.*p;
}

int main() {
  Derived d {10.0};
  print<&Derived::a>(d);
}

问题在于它不起作用,但如果我只使用基类:
template<double (Base::*p)>
void print(Base b) {
  std::cout << b.*p;
}

int main() {
  Base b {10.0};
  print<&Base::a>(b);
}

它按预期工作并打印出 10.0。我如何让它与子类一起工作?

编译器提示:

test.cpp:15:6: note:   template argument deduction/substitution failed:
test.cpp:21:23: error: could not convert template argument ‘&Base::a’ from ‘double Base::*’ to ‘double Derived::*’
   print<&Derived::a>(d);

当您传递一个Derived时,您会得到什么错误?错误通常已经包含了有关错误的良好提示。 - 463035818_is_not_a_number
这个问题与您上一个问题相同,不清楚您想要实现什么,而且示例太简单了,无法看出为什么要使用成员指针作为模板参数。您能解释一下动机吗?有不同的解决方案,但应用哪个取决于您为什么想要将成员指针作为模板参数。 - 463035818_is_not_a_number
我猜测当这种情况发生时,可能正在进行一些探索,而 OP 对此产生了好奇心。我认为(只是猜测)规避问题并不是主要目标,而是要理解为什么代码被拒绝的原因。 - Ted Lyngmo
@TedLyngmo可能是这种情况,当然这并不意味着这不是一个好问题。只有在有具体目标时,答案才会有所不同。似乎“不行,因为”已经足够作为答案了。 - 463035818_is_not_a_number
我明白你的意思。是的,如果我所猜测的是提问的主要原因,那么这个问题本可以表述得更清楚一些。现在当我再读一遍“如何让它与子类一起工作”时,它并不是那么清晰了。 - Ted Lyngmo
3个回答

1

正如其他人所解释的原因,我将解释解决方案:

template<typename T, double (T::*p)>
void print(T d) {
  std::cout << d.*p;
}


int main() {
  Derived d {10.0};
  print<Base, &Base::a>(d);
}

https://gcc.godbolt.org/z/pM762f

我知道这不是你所希望的,但这比每个版本都超载要好。但我认为最好的方法是找到更好的解决问题的方法,因为我知道这里的代码并不是你实际面临的问题,因为你本可以只传递双倍本身而不是成员指针。
因此,请尝试在代码上下文中解决问题,而不是使用这种方式。以下是一些可能解决您实际问题的想法(如果您有任何问题):
- 使用枚举 - 如果不太多,考虑std :: map - 您可以使用lambda表达式(它们现在是constexpr,因此您也可以在模板中使用它们) - 考虑将打印函数移动到基类甚至派生类中 - 使用高阶函数(太多的lambda本质上是:D),而不是整个面向对象的解决方案。
还有许多其他解决方案,其中大多数可能对您没有帮助(因为我不知道您代码的上下文)。

0

这并不是那么明显,但是指向a的成员指针的类型是double Base::*,无论它指向Derived对象中的a还是Base对象中的a。您的方法仅接受Derived参数和Derived中成员的指针作为模板参数。如果您想允许不同的组合,可以编写更多的重载。


为什么如果使用double (Derived::*p)作为函数参数而不是模板参数,即使类型不匹配,它也能正常工作? - walnut
@walnut 很好的问题。也许对于模板参数,隐式转换的规则是不同的... - 463035818_is_not_a_number
即使对于正确的模板参数类型进行显式的 static_cast 转换也无法改变错误信息。因此,我认为这与隐式转换无关。 - walnut
我认为 https://timsong-cpp.github.io/cppwp/n4659/conv.mem#2 应该可以隐式地允许所需的转换(如果 &Derived::a 还没有正确的类型)。 - walnut
算了,我之前提到的转换似乎不适用于模板参数中的隐式转换,但即便如此,显式的 static_cast(我仍然不明白为什么它不起作用)在 gcc 上会产生奇怪的错误信息:https://godbolt.org/z/zDr36s - walnut

0

这将是编译器消息独自具备所有解释的示例之一:

<source>: In function 'int main()':

<source>:21:23: error: no matching function for call to 'print<&Base::a>(Derived&)'
   21 |   print<&Derived::a>(d);
      |                       ^
<source>:15:6: note: candidate: 'template<double Derived::* p> void print(Derived)'
   15 | void print(Derived d) {
      |      ^~~~~
<source>:15:6: note:   template argument deduction/substitution failed:
<source>:21:23: error: '&Base::a' is not a valid template argument for type 'double Derived::*'

   21 |   print<&Derived::a>(d);
      |                       ^
<source>:21:23: note: because it is a member of 'Base'
Compiler returned: 1

回答评论中的问题:模板参数不能隐式转换为基类成员指针,static_cast也无法帮助,因为对于指向成员的指针的模板非类型参数必须以形式&C::m编写(请参见https://en.cppreference.com/w/cpp/language/pointer#Pointers_to_members)。

为什么使用double (Derived::*p)作为函数参数而不是模板参数时它能够工作?这应该也会有一个隐式转换到正确类型,参见https://timsong-cpp.github.io/cppwp/n4659/conv.mem#2。为什么它不适用? - walnut
print<&Derived::a>(d) 在某个地方会变成 print<&Base::a>(d) 吗? - Ted Lyngmo
我上面提到的转换似乎在模板参数中的隐式转换被排除了,但是无论是使用显式的static_cast还是不使用,GCC和Clang都不接受这段代码,为什么呢? - walnut
我仍然不理解。这里提供了更明确的逐步转换到正确类型的方法。print行有什么问题,而print2行没有问题?我使用&C::m初始化变量,cppreference网站上也没有说&C::m需要是模板参数表达式本身。 - walnut
我不理解为什么没有进行隐式转换。原因是什么? - Johannes Schaub - litb
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接