调用非成员函数而不是成员函数

14
我是一个有用的助手,可以为您翻译文本。
我有一个非常简单的问题:在某个地方有一个函数。
int size (const C & c)

至少通过参数相关名称查找找到。现在问题来了:

struct B
{
    int size () { /* ... */ }

    void doSomething (const C & c)
    {
       int x = size (c); // <----------- problem!
       // ...
    }
}

这样做是不起作用的,因为在找到成员函数后名称查找就会停止。

我需要在指定的行中编写什么,以便尝试调用的不是成员函数,而是编译器如果成员函数不存在则执行任何操作?

请注意,解决方案不是编写::size,因为这会阻止参数相关名称查找,并且只有在我知道size声明在哪里时才起作用。

进一步的复杂性:

我知道对于每个我使用下面模板化的成员函数B::doSomething的相关类型T,都将存在一个函数。

int size (const T & t)

这个至少可以通过参数相关的名称查找找到。代码 B 如下所示:
struct B
{
    int size () { /* ... */ }

    template<class T>
    void doSomething (const T & t)
    {
       int x = size (t); // <----------- problem!
       // ...
    }
}

我希望调用非成员函数(我确定它存在,但不确定它在哪里)。


我猜重命名其中一个尺寸函数是不可行的? - Mark B
是的,因为它在逻辑上是相同的操作。 - JohnB
1
所以你想要一种改变函数查找语义的方法。我认为最接近的方法是加入C++工作组之一。 - Lightness Races in Orbit
问题是:已经有改变查找语义的方法了吗? - JohnB
3个回答

15

这是一个众所周知的问题,它的解决方案同样也是众所周知的。我很惊讶还没有被提到。如果你有一个非成员函数,像这样:

class C;
size_t size( C const &c );

您可以使用 using 声明,使名称查找优先于成员函数:

struct B {
  size_t size();

  void foo( C const &c ) {
    using ::size;
    size_t sz = size(c);
  }
};

当编译器看到对`size(c)`的调用时,它从最内层作用域开始向外搜索名为`size`的内容。没有`using`声明,编译器会在类作用域中找到成员函数,而不是全局命名空间中的非成员函数,但是`using`声明改变了这一点。最内层作用域是函数本身,而`using`声明在成员函数之前被找到。
美妙的是,你仍然可以获得参数相关查找(ADL),因为对`size(c)`的实际调用是未限定的。这意味着你可以在模板中使用它:
template <class T>
void foo( T const &c ) {
  using ::size;
  size_t sz = size(c);
}

即使正确的size函数在另一个命名空间中,通过ADL也能找到它。使用声明只需要引用任何一个size函数,不一定是你实际想要的那个。通常会在某处有一个默认实现,可能会调用成员函数。下一个C++标准(C++17)几乎肯定会有一个执行此操作的std::size函数。一旦这个函数广泛可用,您就可以编写:
using std::size;
size_t sz = size(c);

目前,您可以提供自己的默认实现,例如:

template <class C>
constexpr auto size( C const &c ) -> decltype(c.size()) {
  return c.size();
}

你可以参考 C 版本的代码,然后依靠 ADL 来找到正确的版本。


2
那绝对是解决方案。 - JohnB

6

如果您无法重命名自己的成员函数,可以使用一个不太光彩的技巧:

static inline int dirty_trick(C const & c)
{
    return size(c);
}

void B::doSomething(C const & c)
{
    int x = dirty_trick(c);

    // ...
}

由于模板,如果所有东西都必须存在头文件中,这会用肮脏的技巧污染我的命名空间 :-( - JohnB
@JohnB:它应该放在单个TU中,而不是头文件中。否则使用匿名命名空间。 - Kerrek SB
@KerrekSB 为什么不在源文件中也使用匿名命名空间呢? - PiotrNycz
1
我不明白。假设doSomethingtemplate<class T> void doSomething (const T & t),而sizetemplate<class T> int size (const T & t)。那么两者都必须与技巧一起放入头文件中,这个技巧必然会导致污染?即使使用匿名命名空间,我认为也是如此。 - JohnB
不,从技术上讲并不是这样。我的意思是:如果我在头文件中写入 namespace { template<class T> int dirty_trick (...) ... },那么1.) 我做了一些不应该做的事情,因为我不应该在头文件中使用匿名命名空间,2.) 在头文件被包含后,dirty_trick 仍然是可见的,因为它是一个TU,所以匿名命名空间是无用的。或者我错了吗? - JohnB
显示剩余3条评论

3

为了完整起见,补充接受Richard Smith的答案:

现在我的解决方案如下:

namespace adl {
   // This declaration's only purpose is the possibility to refer to
   // a non-member function named "size" in a using declaration.
   //
   // The signature does not matter, so we choose the easiest possible one.
   void size ();
}

struct B
{
    int size () { /* ... */ }

    template<class T>
    void doSomething (const T & t)
    {
       using adl::size;
       int x = size (t); // <----------- no problem anymore
       // ...
    }
};

这样我就不必包含可能并非必需的任何头文件。


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