Может ли нетипизированный параметр шаблона иметь тип "void *"?

Якк - Адам Невраумонт сказал:

Нетипичные параметры шаблона типа void* недопустимы по крайней мере в некоторых версиях стандарта.

Это правда? Если это так, то в каких версиях стандарта не допускаются параметры шаблона нетипичного типа типа void*?

(Примечание: как отмечено в комментарии для ответа на другой комментарий, речь идет о нетиповых параметрах шаблона, а не аргументах типа шаблона, которые могут быть любым допустимым идентификатором типа для [temp.arg.type], включая void*.

Ответы

Ответ 1

TL; DR

Параметры шаблона типа void* действительны начиная с С++ 20. Они недействительны до С++ 20.

С++ 20

С++ 20 ослабил ограничения на тип нетипичного параметра шаблона, поэтому давайте сначала исследуем его.

В текущем проекте (по состоянию на UTC 10:00, 6 мая 2019 г.) говорится в [temp.param]/4:

Нетипизированный шаблон-параметр должен иметь один из следующих (необязательно квалифицированных cv) типов:

  • литеральный тип, имеющий сильное структурное равенство ([class.compare.default]),
  • тип ссылки lvalue,
  • тип, который содержит тип заполнителя ([dcl.spec.auto]), или
  • заполнитель для выведенного типа класса ([dcl.type.class.deduct]).

void* - это тип указателя. Тип указателя является скалярным типом ([basic.types]/9). Скалярный тип является литеральным типом ([basic.types]/10). Следовательно, void* является литеральным типом. Первая пуля является актуальной.

Отслеживая далее, [class.compare.default]/3 говорит:

Тип C имеет сильное структурное равенство, если при заданном значении x типа const C либо:

  • C - это неклассный тип, а x <=> x - допустимое выражение типа std::strong_ordering или std::strong_equality, или

  • C - это тип класса с оператором == определенным как значение по умолчанию в определении C, x == x правильно сформирован, когда контекстно преобразован в bool, все подобъекты базового класса C и нестатические члены-данные имеют сильное структурное равенство, и C не имеет mutable или volatile подобъектов.

void* относится к классу, поэтому первый пункт имеет значение. Теперь вопрос сводится к типу x <=> x где x - это glvalue типа void* const (не const void*). За [expr.spaceship]/8:

Если тип составного указателя является типом указателя объекта, p <=> q имеет тип std::strong_ordering. Если два операнда-указателя p и q сравниваются равными ([expr.eq]), p <=> q std::strong_ordering::equal; если p и q сравниваются неравно, p <=> q std::strong_ordering::less если q сравнивает больше, чем p и std::strong_ordering::greater если p сравнивает больше, чем q ([expr.rel]). В противном случае результат не уточняется.

Обратите внимание, что void* является типом указателя на объект ([basic.compound]/3). Следовательно, x <=> x имеет тип std::strong_ordering. Таким образом, тип void* имеет сильное структурное равенство.

Поэтому в текущем черновике С++ 20 void* допускается как тип параметра типа шаблона.

С++ 17

Теперь мы обращаемся к С++ 17. [temp.param] говорит:

Нетипизированный шаблон-параметр должен иметь один из следующих (необязательно квалифицированных cv) типов:

  • целочисленный или перечислимый тип,
  • указатель на объект или указатель на функцию,
  • lvalue ссылка на объект или lvalue ссылка на функцию,
  • указатель на член,
  • std​::​nullptr_t или
  • тип, который содержит тип заполнителя.

Обратите внимание, что "указатель на объект" не включает void* per [basic.compound]/3:

[Примечание: указатель на void не имеет типа указатель на объект, потому что void не является типом объекта. - конец примечания]

Ни одна из шести вышеприведенных позиций не включает void* в качестве возможного типа параметра шаблона. Поэтому в С++ 17 параметр шаблона не должен иметь тип void*.

Формулировка одинакова для С++ 11 и С++ 14, за исключением того, что в ней нет описания типов заполнителей. В общем, до С++ 20 параметр шаблона не должен иметь тип void*.

Но компиляторы диагностируют это?

В комментарии TC говорится, что никто не ставит диагноз этому IHRC. Давайте проверим, диагностируют ли это компиляторы в режиме С++ 17 с минимальным примером, показанным ниже:

template <void*>
class C {};

int main()
{
    C<nullptr> x;
    (void) x;
}

Код компилируется и отлично работает на GCC 9.1.0, GCC 8.3.0, GCC 7.3.0, GCC 6.3.0, GCC 5.5.0, Clang 8.0.0, Clang 7.0.0, Clang 6.0.1 и Clang 5.0.0.

Натан Оливер сказал мне в комментарии, что кто-то сказал ему, что некоторые компиляторы будут ошибаться, но основные - нет. Поэтому, насколько я могу подтвердить здесь, утверждение ТС верно - никто не диагностирует это.