Перечисления и указатели на члены - PullRequest
11 голосов
/ 05 марта 2011

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

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

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

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

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

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

Вопросы:

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

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

1 Ответ

2 голосов
/ 05 марта 2011

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

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

...