Перечисления и указатели

Недавно я попытался создать класс is_class и потребовал, чтобы компилятор мог различать типы перечислений и типы классов, для которых определены операторы преобразования. Учитывая, что классы, структуры и объединения являются единственными типами, совместимыми с функциями-указателями-членами, я решил, что компилятор определит, был ли тип, используемый для создания экземпляра шаблона is_class, в свою очередь, совместим с указателями- функции-члены. После нескольких проблем, я решил проверить поведение перечислений при использовании в сочетании с указателями между собой и получил некоторые дурацкие результаты. Следующий сегмент иллюстрирует первый приступ:

enum ENUM {};
void Test(void (ENUM::*pmem) (void))
{
    /* ... */
}
Test(NULL);

При компиляции с Microsoft Visual С++ 2010 часть указателя на элемент определения функции: (ENUM::*pmem)

выделяется красным цветом, а mousing над объявлением обнаруживает ошибку:

Error: "ENUM" is not a class type

Однако компилятор анализирует этот сегмент без каких-либо ошибок, присваивая pmem NULL. Мне интересно, что компилятор разрешил бы это видеть, как типы перечисления не являются классами, структурами или объединениями и поэтому не могут обладать собственными методами.

Вторая интересная задача возникла при создании функции шаблона, взяв аргумент-указатель-член, тип которого варьируется:

template<class _Ty>
void Test_Template(void (_Ty::*pmem) (void))
{
    /* ... */
}

Конечно, чтобы использовать эту функцию, она должна быть явно определена:

Test_Template<ENUM>(NULL);

Этот вызов, однако, генерирует сообщение об ошибке:

invalid explicit template argument(s) for 'void Test(void (__thiscall _Ty::* )(void))'

Я исправил эту проблему, создав дополнительный шаблон функции, прототип которого соответствовал бы любому вызову, который не соответствовал прототипу предыдущей функции шаблона (в которой использовалось многоточие).

Вопросы:

  • Почему перечисление совместимо с указателями-членами?

  • Почему существует точное совпадение при вызове функции non-template Test, когда компилятор генерирует ошибку для явной квалификации шаблона Test_Template?

Ответ 1

Что касается вашего первого вопроса, кажется, что компилятор действительно сообщает, что перечисления не могут иметь функции-члены, поскольку компилятор сообщает об ошибке в объявлении функции. Вероятно, это позволяет успешно выполнить вызов, внутренне пытаясь как можно больше исправить плохую декларацию, которая в этом случае означает, что вы пытаетесь объявить что-то вроде указателя и разрешить вызов. Нет требования, чтобы компилятор дал вам ошибку в этой строке; так как программа I'll-shaped, если компилятор отклоняет программу с помощью диагностики, ей не нужно давать ошибки везде.

Что касается вашего второго вопроса, причина того, что наличие второго шаблона приводит к ошибке, является "отказ замены не является ошибкой" (SFINAE). Когда компилятор создает экземпляр шаблона функции с некоторыми аргументами типа, если он обнаруживает, что конкретное действие функции недействительно (например, попытка получить указатель на член перечисления), он не сообщает об ошибке. Вместо этого он просто удаляет этот шаблон из соображений. Если, однако, ни один из шаблонов, которые вы написали, не действителен при создании экземпляров с данными аргументами, тогда компилятор выдаст ошибку am из-за того, что не может найти соответствия тому, что вы пытаетесь сделать. В первом случае, когда у вас есть только один шаблон, возникает ошибка, потому что SFINAE исключает единственного кандидата шаблона из соображений, в результате чего у экземпляра шаблона не будет подходящего шаблона. Во втором случае ваш шаблон "catch-all" по-прежнему действителен после того, как вы создаете экземпляр шаблона, поэтому, когда шаблон, переводящий указатель в член, исключается, все еще существует правовой шаблон, на который вы можете ссылаться. Следовательно, код отлично подходит.