Сигнатуроподобные выражения функции как аргументы шаблона C++
Я смотрел на мини-библиотеку FastDelegate Дона Клагстона и заметил странный синтаксический трюк со следующей структурой:
TemplateClass< void( int, int ) > Object;
Похоже, что сигнатура функции используется в качестве аргумента объявления экземпляра шаблона.
Этот метод (чье присутствие в FastDelegate, по-видимому, связано с одним Джоди Хагинсом) был использован для упрощения объявления экземпляров шаблона с полу произвольным числом параметров шаблона.
Это позволило сделать что-то вроде следующего:
// A template with one parameter
template<typename _T1>
struct Object1
{
_T1 m_member1;
};
// A template with two parameters
template<typename _T1, typename _T2>
struct Object2
{
_T1 m_member1;
_T2 m_member2;
};
// A forward declaration
template<typename _Signature>
struct Object;
// Some derived types using "function signature"-style template parameters
template<typename _Dummy, typename _T1>
struct Object<_Dummy(_T1)> : public Object1<_T1> {};
template<typename _Dummy, typename _T1, typename _T2>
struct Object<_Dummy(_T1, _T2)> : public Object2<_T1, _T2> {};
// A. "Vanilla" object declarations
Object1<int> IntObjectA;
Object2<int, char> IntCharObjectA;
// B. Nifty, but equivalent, object declarations
typedef void UnusedType;
Object< UnusedType(int) > IntObjectB;
Object< UnusedType(int, char) > IntCharObjectB;
// C. Even niftier, and still equivalent, object declarations
#define DeclareObject( ... ) Object< UnusedType( __VA_ARGS__ ) >
DeclareObject( int ) IntObjectC;
DeclareObject( int, char ) IntCharObjectC;
Несмотря на реальный привкус хакерства, я нахожу этот вид поддельной эмуляции аргументов вариационных шаблонов довольно сногсшибательным.
Реальный смысл этого трюка заключается в том, что я могу передавать текстовые конструкции типа "Тип1(Тип2, Тип3)" в качестве аргументов для шаблонов. Итак, вот мои вопросы: как именно компилятор интерпретирует эту конструкцию? Это сигнатура функции? Или это просто текстовый шаблон с круглыми скобками? Если первое, то означает ли это, что любая сигнатура произвольной функции является допустимым типом в отношении процессора шаблонов?
Последующий вопрос заключается в том, что, поскольку приведенный выше пример кода является допустимым кодом, почему стандарт C++ не позволяет вам делать что-то вроде следующего, которое не компилируется?
template<typename _T1>
struct Object
{
_T1 m_member1;
};
// Note the class identifier is also "Object"
template<typename _T1, typename _T2>
struct Object
{
_T1 m_member1;
_T2 m_member2;
};
Object<int> IntObject;
Object<int, char> IntCharObject;
2 ответа
Что касается вашего первого вопроса - о типе int(char, float)
- это допустимый тип C++ и тип функции, которая принимает char
и float
и возвращает int
, Обратите внимание, что это тип фактической функции, а не указатель функции, который был бы int (*) (char, float)
, Фактический тип любой функции - это необычный тип. Например, тип
void DoSomething() {
/* ... */
}
является void ()
,
Причина, по которой это не так часто встречается во время рутинного программирования, заключается в том, что в большинстве случаев вы не можете объявлять переменные этого типа. Например, этот код недопустим:
void MyFunction() {
void function() = DoSomething; // Error!
}
Однако, один случай, когда вы действительно видите используемые типы функций, - это передача указателей на функции:
void MyFunction(void FunctionArgument()) {
/* ... */
}
Чаще встречается такая функция, написанная для получения указателя на функцию, но она прекрасно подходит для самой функции. Это брошено за кулисы.
Что касается вашего второго вопроса, почему незаконно иметь один и тот же шаблон, написанный с разным количеством аргументов, я не знаю точно формулировку в спецификации, которая запрещает его, но это как-то связано с тем, что, как только вы объявил шаблон класса, вы не можете изменить количество аргументов для него. Однако вы можете предоставить частичную специализацию для этого шаблона с другим количеством аргументов, при условии, конечно, что частичная специализация специализируется только на исходном количестве аргументов. Например:
template <typename T> class Function;
template <typename Arg, typename Ret> class Function<Ret (Arg)> {
/* ... */
};
Вот, Function
всегда принимает один параметр. Специализация шаблона принимает два аргумента, но специализация по-прежнему распространяется только на один тип (в частности, Ret (Arg)
).
int* int_pointer; // int_pointer has type "int*"
int& int_reference; // int_reference has type "int&"
int int_value; // int_value has type "int"
void (*function_pointer)(int, int); // function_pointer has type
// "void (*)(int, int)"
void (&function_reference)(int, int); // function_reference has type
// "void (&)(int ,int)"
void function(int, int); // function has type
// "void(int, int)"
template<>
struct Object1<void(int, int)>
{
void m_member1(int, int); // wait, what?? not a value you can initialize.
};