能否在编译时不知道返回类型的情况下声明指向函数的指针?

26

我有一个名为class A的类,我想在其中拥有一个指向函数的指针作为数据成员:

class A
{
  protected:
    double (*ptrToFunction) ( double );

  public:
  ...
  //Setting function according to its name
  void SetPtrToFunction( std::string fName );
};

但是如果我想让 ptrToFunction 有时是 double,有时是 int,应该怎么做呢?可以像这样:

//T is a typename
T(*ptrToFunction) ( double );

在这种情况下,我应该如何声明它?


8
你想如何使用这个功能? - n. m.
6
更重要的是,呼叫者如何知道它期望得到什么回复? - pjc50
3个回答

25
一个区分联合体可以帮助你实现这个功能:
class A
{
  template<T>
  using cb_type = T(double);

  protected:
     enum {IS_INT, IS_DOUBLE} cb_tag;
     union {
       cb_type<int>    *ptrToIntFunction;
       cb_type<double> *ptrToDoubleFunction;
     };

  public:
  ...
  // Setting function according to its name
  void SetPtrToFunction( std::string fName );
};

在C++17中,可以使用std::variant或在早期标准修订版中使用boost::variant来应用更通用和优雅的解决方案来处理区分联合。
或者,如果您想要完全忽略返回类型,可以将成员转换为std::function<void(double)>并从类型抹除中获益。 Callable概念将通过指针调用转换为static_cast<void>(INVOKE(...))并丢弃无论它是什么的返回值。
为了说明:
#include <functional>
#include <iostream>

int foo(double d)  { std::cout << d << '\n'; return 0; }

char bar(double d) { std::cout << 2*d << '\n'; return '0'; }

int main() {
    std::function<void(double)> cb;

    cb = foo; cb(1.0);

    cb = bar; cb(2.0);

    return 0;
}

最后,如果您关心返回值但不想存储一个带判别的联合体(discriminated union),那么了解联合体和 std::function 的行为,您可以结合上述两种方法。

像这样

#include <functional>
#include <iostream>
#include <cassert>

int    foo(double d) { return d; }

double bar(double d) { return 2*d; }

struct Result {
    union {
        int    i_res;
        double d_res;
    };
    enum { IS_INT, IS_DOUBLE } u_tag;

    Result(Result const&) = default;
    Result(int i)  : i_res{i}, u_tag{IS_INT} {}
    Result(double d) : d_res{d}, u_tag{IS_DOUBLE} {}

    Result& operator=(Result const&) = default;
    auto& operator=(int i)
    { i_res = i; u_tag = IS_INT;    return *this; }
    auto& operator=(double d)
    { d_res = d; u_tag = IS_DOUBLE; return *this; }
};

int main() {
    std::function<Result(double)> cb;

    cb = foo;
    auto r = cb(1.0);
    assert(r.u_tag == Result::IS_INT);
    std::cout << r.i_res << '\n';

    cb = bar;
    r = cb(2.0);
    assert(r.u_tag == Result::IS_DOUBLE); 
    std::cout << r.d_res << '\n';

    return 0;
}

2
不介意的话,这有点复杂难懂。一些内联注释会有所帮助。cb_tag的用途是什么? - Saurav Sahu
1
使用std::function擦除返回类型是一个好主意。+1 - skypjack
抱歉,但我不理解在使用带鉴别联合体的第一种解决方案中,cb_tag和union有什么关联。我已经阅读了您提供的文章,但仍不明白如果我将cb_tag设置为IS_DOUBLE并尝试在union的活动成员为int时返回double会发生什么。我没有看到它们之间的任何联系。 - paraxod
1
@paraxod - 这个标签是让你知道联合体保存了什么。你的工作是做好账务管理。没有任何魔法可以使标签与活动成员匹配。如果你不这样做,你的程序将会有未定义的行为。 - StoryTeller - Unslander Monica
@paraxod - 我认为最后一个解决方案可能是最复杂的。但它也充分展现了标记联合的大部分簿记工作。其余部分由std::function的精彩规范完成。 - StoryTeller - Unslander Monica
显示剩余3条评论

1
如果你的类没有模板,就像你的例子一样,你可以这样做:
template <class T>
struct myStruct
{
  static T (*ptrToFunction)(double);
};

为什么要使用静态变量?值得注意的是,这会限制每个实例只能返回一个返回类型(如果去掉静态变量)。当然,这可能正是所需的。 - Steve Kidd

1

看起来您正在使用dlsym / GetProcAddress来获取函数地址。

在这种情况下,您需要至少两个调用点来消除歧义,因为CPU实际上对这些调用执行不同的操作。

enum ReturnType { rtInt, rtDouble };
void SetPtrToFunction( std::string fName , enum ReturnType typeOfReturn );

struct Function {
   enum ReturnType rt;
   union {
          std::function< int(double) > mIntFunction;
          std::function< double(double) > mDoubleFunction;
   } u;
} mFunction;

因此,该函数需要使用已知的返回类型进行实例化,然后将其与某些标记联合使用以获取正确的函数调用。
  int A::doCall( double value ) {
      if( mFunction.rt == rtInt ) {
          int result = mFunction.mIntFunction( value );
      } else if( mFunction.rt == rtDouble ) {
          double result = mFunction.mDoubleFunction( value );
      }
  }

你指的是dlsym(不是dlfun),并且你可能指的是原始函数指针,而不是std::function - Basile Starynkevitch
我在问题中没有看到任何暗示动态库调用的内容。 - Steve Kidd
@SteveKidd,我并不熟悉除使用GetProcAddress/dlsym之外将字符串与函数关联的另一种机制。 - mksteve
@kmsteve 抱歉 - 我明白你的观点了。另一种选择是硬编码 if/else 语句,根据输入字符串的值设置函数指针。 - Steve Kidd

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