如何阅读模板函数指针声明中的众多星号和括号?

12

来自C++11新特性介绍:后置返回类型

该文章声称:

template <class T> class tmp {
public:
    int i;
};

auto foo()->auto(*)()->tmp<int>(*)(){
    return 0;
}

等同于

template <class T> class tmp{
public:
    int i;
};

tmp<int> (*(*foo())())() {
    return 0;
}

我不理解第二个代码示例中的复杂函数。我应该从哪里开始看呢?我猜是 foo。但是紧挨着 foo 的那个语句将会把 foo 定义为一个指针...... 根据第一个代码示例,我将把这一段转换为:

tmp<int> (*)() (*)() foo(){ return 0;}

所以foo是一个函数,它返回0,但返回类型有些棘手:返回类型是一个函数指针,其返回类型又是一个函数指针,其返回类型是tmp<int>


你正在创建一个函数指针,因此必须在函数中定义所有内容,* 用于指针和指针的解引用。 - Jake Freeman
3
如果你正确地进行代码缩进,它将更容易阅读。 ;) - Oliver Charlesworth
1
可能是复杂的C声明的重复问题,或者是https://dev59.com/PnI-5IYBdhLWcg3wW3Gp或https://dev59.com/u3M_5IYBdhLWcg3wRw51等问题。 - underscore_d
5
我不理解第二个代码示例中的复杂函数。这正是问题所在;除非接受过特定的训练,否则复杂函数声明无法阅读。因此请使用替代方案。 - Yakk - Adam Nevraumont
4个回答

24
我应该从哪里开始看呢? 说实话,你只需要看 https://cdecl.org/ 上对于int (*(*foo())())();的描述: "declare foo as function returning pointer to function returning pointer to function returning int" 然后意识到这是C++11,我们有一个非常好的语法来声明函数指针别名:
using A = int(*)(); // pointer to function returning int
using B = A(*)();  // pointer to function returning pointer to function returning int

B foo(); // function returning pointer to function returning pointer to function returning int

今天实际上没有理由像那样写声明。


我相信你也可以使用 typedef 实现这个,但我不能确定。我也相当肯定它在 C++ 中也起作用。 - anon
2
@QPaysTaxes 的重点是拥有一个好看的语法。typedef int(*A)(); 不是一个好看的语法。 - Barry
没错,但是你在C语言中没有这个,我刚意识到我不小心称呼它为C++了。哎呀。 - anon
@QPaysTaxes 是的,这就是为什么它被称为 C++。它比 C 更好 :-) - Barry
比一页长得多,明显更长。 - anon

17

补充一下@Vittorio的回答,这里有一个Clockwise Spiral Rule可以帮助我们解密复杂类型:

从未知元素开始,顺时针螺旋移动;遇到以下元素时,用相应的英语语句替换它们:

  • [X][]

    数组X大小为...或数组大小未定义...

  • (type1,type2)

    传递类型1和类型2并返回函数...

  • *

    指向...

以顺时针螺旋方式继续执行,直到涵盖所有标记。始终首先解决括号内的任何内容!


这里:

           +-----------+
           | +------+  |
           | | >-v  |  |
temp<int> (*(*foo())())()
        |  | ^---+  |  |
        |  ^--------+  |
        +--------------+

foo 是一个返回指向返回指向返回 temp<int> 的函数指针的函数指针的函数。


现在,@UKmonkey刚刚将此规则重命名为C++ Guru Snail Rule或简称CGSR:

 / /
 L_L_
/    \
|00  |       _______
|_/  |      /  ___  \
|    |     /  /   \  \
|    |_____\  \_  /  /
 \          \____/  /_____
  \ _______________/______\.............................

1
该死,要是 ASCII 艺术能够编译就好了... - Chris O

5

cdecl 是一个有用的在线工具,用于解析复杂的C语言声明。

插入 int (*(*foo())())() 返回:

声明 foo 为一个返回指向函数的指针的函数的指针,该函数返回指向int的指针。

由于该工具不支持模板,我已将 tmp<int>替换为 int


5

正确格式化代码有助于您理解:

template <class T>
class tmp {
    public:
    int i;
};

auto foo() -> auto(*)() -> tmp<int>(*)() {
    return 0;
}

template <class T>
class tmp{
    public:
    int i;
};

tmp<int> (*
    ( *foo() )()
    )() {
    return 0;
}
template class 部分保持不变,因此我不打算对其进行详细说明。我们来看看函数 foo
在第一段代码中,foo() 的返回值为 auto(*)() -> tmp<int>(*)(),它是指向返回另一个指针的函数的指针,该指针又指向返回 tmp<int> 的函数。
正如您总是可以像这样定义一个函数指针:
base_type_t (*pointer_name)(parameter_list);

递归使用函数(即func_name())来定义一个返回指针的函数,可以声明pointer_name
base_type_t (*func_name())(parameter_list);
              ~~~~~~~~~~~

所以现在(*func_name())(parameter_list)可以用于另一个函数。让我们把它放回到函数指针定义语法中:

base_type_t (*(*func_name())(parameter_list))(parameter_list);
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

清空参数列表(它们为空),并用正确的类型替换标识符,得到:
base_type_t (*(*func_name())(parameter_list))(parameter_list);
tmp<int>    (*(*   foo   ())( /* Empty */  ))( /* Empty */  );

// Turns to
tmp<int> (*(*foo())())();

正如其他人所建议的,https://cdecl.org/ 是一个很好的代码分析器,但它可能会给出另一句话,不太容易理解。


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