Как читать так много звезд и круглых скобок в шаблонном объявлении функции-указателя?

Из Введение в функцию С++ 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. Но stat рядом с foo будет определять foo как указатель... Основываясь на первом примере кода, я преобразую фрагмент как

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

Итак, foo - это функция, которая возвращает 0, но возвращаемый тип является сложным: его тип возврата - это функциональный указатель, возвращаемый тип которого снова является указателем функции, тип возврата которого tmp<int>.

Ответ 1

В дополнение к ответу @Vittorio существует правило по часовой стрелке, чтобы помочь нам дешифровать сложные типы:

Начиная с неизвестного элемента, двигайтесь по спирали/по часовой стрелке; при встрече со следующими элементами заменяйте их соответствующими английскими утверждениями:

  • [X] или []

    Массив X размер... или массив undefined размер...

  • (type1, type2)

    Функция, передающая тип1 и возвращаемый тип2...

  • *

    указатель для...

Продолжайте делать это по спирали/по часовой стрелке до тех пор, пока все маркеры не будут закрыты. Всегда сначала разрешайте что-либо в скобках!


Здесь:

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

foo - это функция, возвращающая указатель на функцию, возвращающую указатель на функцию, возвращающую temp<int>.


И теперь, @UKmonkey просто переименовал это правило. С++ Guru Snail Rule или CGSR для краткости:

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

Ответ 2

Где я должен смотреть вначале?

Честно говоря, вы должны просто взглянуть на https://cdecl.org/, который описывает int (*(*foo())())(); как:

объявить foo как функцию, возвращающую указатель на функцию, возвращающую указатель на функцию, возвращающую int

И затем поймите, что это С++ 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

На самом деле нет причин писать такие объявления сегодня.

Ответ 3

cdecl - полезный онлайн-инструмент для демистификации сложных деклараций C.

Вставка int (*(*foo())())() возвращает:

объявить foo как функцию, возвращающую указатель на функцию, возвращающую указатель на функцию, возвращающую int

Я заменил tmp<int> на int, поскольку инструмент не поддерживает шаблоны.

Ответ 4

Правильное форматирование кода может помочь вам понять:

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);

Рекурсия pointer_name с помощью функции (т.е. func_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/ - хороший анализатор кода, хотя он может дать вам еще одно предложение, которое не так просто понять.