【问题标题】:C++11: How to alias a function? [duplicate]C++11:如何给函数起别名? [复制]
【发布时间】:2012-04-09 11:37:25
【问题描述】:

如果我在命名空间栏中有一个类 Foo:

namespace bar
{
    class Foo { ... }
};

然后我可以:

using Baz = bar::Foo;

现在就像我在命名空间中使用名称 Baz 定义的类一样。

是否可以对函数做同样的事情?

namespace bar
{
    void f();
}

然后:

using g = bar::f; // error: ‘f’ in namespace ‘bar’ does not name a type

最干净的方法是什么?

该解决方案也应该适用于模板函数。

定义:如果某个实体 B 是 A 的 别名,那么如果 A 的任何或所有用法(当然不是声明​​或定义)在源代码比(剥离)生成的代码保持不变。例如typedef A B 是一个别名。 #define B A 是一个别名(至少)。 T& B = A 不是别名,B 可以有效地实现为间接指针,而“未别名”的 A 可以使用“立即语义”。

【问题讨论】:

  • @DavidRodríguez-dribeas:上面的“别名”是什么意思几乎没有混淆。一般来说,如果 B 是 A 的别名,那么如果将 A 的用法替换为 B,则生成的代码将保持不变。为什么你想要这个也很简单。我想为库函数提供第二个名称/命名空间。我怀疑最干净的方法是使用新名称的 always_inline 函数包装对旧名称的调用。包装器将被编译出来,根据需要留下一些与直接调用旧名称没有区别的东西。
  • @DavidRodríguez-dribeas:函数指针会产生与普通函数调用不同的代码,因为函数指针需要在调用之前取消引用。
  • @DavidRodríguez-dribeas:再说一次,生成的代码。请参阅上面我定义别名含义的第二条评论。
  • @DavidRodríguez-dribeas:在什么情况下将函数调用包装在内联函数中的解决方案不会产生相同的生成代码?
  • @DavidRodríguez-dribeas:正如我所说的“always_inline”指的是强制内联的属性。请参阅 GCC 手册中的 6.39 内联函数与宏一样快部分。

标签: c++ linux gcc c++11


【解决方案1】:

您可以使用完美转发来定义函数别名(有一些工作):

template <typename... Args>
auto g(Args&&... args) -> decltype(f(std::forward<Args>(args)...)) {
  return f(std::forward<Args>(args)...);
}

即使f 被重载和/或函数模板,此解决方案也适用。

【讨论】:

  • 如果f是不能使用类型推导的函数模板则不适用。
  • @user1131467 任何使用模板参数来确定其返回类型的函数(如boost::lexical_cast),或使用模板参数作为de facto 参数(如@987654326 @)。
  • @RMartin 如果它假定“ExplicitArgs”为空,如果您不指定它可以将其重写为template&lt;typename ...ExplicitArgs, typename ...Args&gt; void foo(Args...args) 并调用另一个类似f&lt;ExplicitArgs...&gt;(args...)。不幸的是,它只对尾随模板参数包执行“假定为空”,而不是对前一个尾随参数包。
  • @RMartin 实际上 GCC 和 Clang 已经做到了这一点,因此上述 ExplicitArgs 方式甚至可以在野外使用!他们认为这两个模板参数包都是“尾随”的。
  • 这会丢弃 noexcept 信息,因此不再是 C++17 中的精确别名:coliru.stacked-crooked.com/a/f3ee222a98c97184
【解决方案2】:

constexpr 函数指针可以用作函数别名。

namespace bar
{
    int f();
}

constexpr auto g = bar::f;

使用g 很可能(但语言不保证)直接使用bar::f。 具体来说,这取决于编译器版本和优化级别。

以下情况尤其如此:

  • GCC 4.7.1+,没有优化,
  • Clang 3.1+,没有优化,
  • MSVC 19.14+,经过优化。

assembly generated by these compilers

【讨论】:

  • 这是最好的答案,但您应该补充一点,这不是真正的别名 - 实际上允许编译器不内联函数调用并进行真正的调用。但我相信大多数编译器都会对其进行优化,如 Compiler Explorer 所示。
  • @jaskmar,我更新了答案,假设您的意思是间接/直接调用而不是内联。内联在这里不相关。
【解决方案3】:

类是类型,因此它们可以使用typedefusing(在C++11 中)作为别名。

函数更像对象,因此没有机制可以为它们起别名。充其量你可以使用函数指针或函数引用:

void (*g)() = &bar::f;
void (&h)() = bar::f;

g();
h();

同样,变量没有别名机制(缺少通过指针或引用)。

【讨论】:

  • @user1131467:您声明并定义了一个新对象。
  • 该对象的类型是什么?这里发生了什么:inline void g() { bar::f(); }
  • Bashphorism 2 警报?!最后一个定义了一个全新的函数。
  • @Hurkyl:函数引用和指针之间没有真正的区别,因为前者总是立即衰减为后者。你可以说foo()(*foo)()(**foo)(),随心所欲。
  • @KerrekSB 我是说如果你有void f(); void (&amp;rf)() = f;,那么&amp;f&amp;rf 都会产生f 的地址。但是如果rf是一个函数指针void (*rf)() = f;,那么&amp;rf返回它的地址而不是函数的地址,这是不好的。
【解决方案4】:

可以在不更改名称的情况下将函数引入不同的范围。因此,您可以使用不同的限定名称为函数起别名:

namespace bar {
  void f();
}

namespace baz {
  using bar::f;
}

void foo() {
  baz::f();
}

【讨论】:

  • 我不知道为什么这不是公认的答案:我只是尝试过它并且它可以工作,即使对于模板函数也是如此(不需要前缀模板 这样做)。据我所知,它会引入名称的所有重载。
  • 这是最好的解决方案,但不适用于内联: namespace bar { static inline __attribute__((always_inline)) void f(); } 命名空间 baz { 使用 bar::f; } 无效 foo() { baz::f(); } $ g++ -g -std=gnu++11 -x c++ -Wall -Wextra -c tns.C xC:3:8: 警告:内联函数 'void bar::f()' 使用但从未定义 void f( ); ^ tns.C:在函数'void foo()'中:tns.C:3:8:错误:调用always_inline'...'时内联失败:函数体不可用xC:11:11:错误:调用自这里是 baz::f();
  • 命名空间栏 { 静态内联 __attribute__((always_inline)) void f(); } 命名空间 baz { 使用 bar::f; } 无效 foo() { baz::f(); }
【解决方案5】:

绝对:

#include <iostream>

namespace Bar
{
   void test()
   {
      std::cout << "Test\n";
   }


   template<typename T>
   void test2(T const& a)
   {
      std::cout << "Test: " << a << std::endl;
   }
}

void (&alias)()        = Bar::test;
void (&a2)(int const&) = Bar::test2<int>;

int main()
{
    Bar::test();
    alias();
    a2(3);
}

试试:

> g++ a.cpp
> ./a.out
Test
Test
Test: 3
>

引用是现有对象的别名。
我刚刚创建了一个函数的引用。引用的使用方式与原始对象完全相同。

【讨论】:

  • 在调用别名时这不需要额外的取消引用吗?这对模板函数有什么作用?
  • 如您所见,它运行良好。使用模板,它适用于特定的实例化,您不能为模板设置别名,但可以为模板的特定版本设置别名。
  • 一个更好的习惯用法是:constexpr auto &amp;alias = Bar::test;constexpr 部分保证引用将在编译时被替换。不过,auto 部分不适用于模板化函数。
  • "引用是现有对象的别名。我刚刚创建了对函数的引用。" -- 并且函数不是对象。因此,对函数的引用不是现有对象的别名(就像函数指针不是对象指针一样):-)
  • @RichardSmith 您应该将该评论转换为答案 - 太好了!
【解决方案6】:

它不是标准的 C++,但大多数编译器都提供了这样做的方法。使用 GCC,您可以这样做:

void f () __attribute__ ((weak, alias ("__f")));

这将创建符号f 作为__f 的别名。使用 VC++,您可以通过这种方式做同样的事情:

#pragma comment(linker, "/export:f=__f")

【讨论】:

  • 这是否适用于重载函数、模板等所有内容?
  • @einpoklum 是的 - 是的。您可以让编译器为其生成的任何具体函数发出别名——毕竟,它们只是可执行对象中的入口点,并为其分配了名称。重载的函数只是函数,名称被修改以反映函数签名。模板函数本身没有可执行名称,但这些模板函数的 实例化 有 - 并且编译器可以为它们发出别名。对你有没有用是另一回事。
  • 这样做的一个困难是您必须在alias( ) 声明中为函数使用损坏的符号名称。您的示例看起来错误(因此);或者即使它在某些系统上是正确的,但对于带参数的函数来说,它会复杂得多..
  • @Davmac:仅当您希望别名是一个错位名称时。创建一个未修改的别名并没有错,尽管它在 C++ 以外的语言中会更有用。
  • @Tom 我并不是说别名会被破坏(或不会)。有问题的是您想将其别名为 to 的符号。如果它被损坏,那么您必须使用损坏的名称来创建别名。该问题询问有关为 bar::foo 创建别名的问题,因此您在创建别名时必须使用该符号的错位名称。例如,它不会是“__foo”,更像是“_ZN3bar3fooEv”。
【解决方案7】:

你可以使用好的旧宏

namespace bar
{
    void f();
}

#define f bar::f

int main()
{
    f();
}

【讨论】:

  • 这可能会产生非常意想不到的效果,具体取决于宏的名称。请参阅this answer 了解原因(尤其是“宏没有命名空间”在这里很危险)。
  • 宏是旧的,但在 C++ 中还差得远。
  • 显然在使用宏时需要小心。每个体面的 C++ 程序员都知道这一点。我不建议在标题中定义这样的宏。但是在源文件中应该没问题。使用宏,您总是可以在脚下射击自己。如果您的技术堆栈允许,您可能应该使用 Paweł Bylica 的答案。但是对于较旧的编译器,您可以考虑使用此解决方案来稍微简化您的生活。
猜你喜欢
  • 1970-01-01
  • 2010-12-13
  • 2017-05-30
  • 2013-07-27
  • 1970-01-01
  • 1970-01-01
  • 2021-11-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多