шаблон с лямбдой в качестве уникального параметра по умолчанию для каждого экземпляра
Я ищу способ автоматически сделать параметр шаблона по умолчанию уникальным каждый раз, когда создается экземпляр шаблона. Поскольку объекты безымянных функций, создаваемые лямбда-выражениями, имеют разные типы, я подумал о том, чтобы как-то их принять. В связи с недавними изменениями в стандартном отчуждении, снятие ограничения "лямбда-выражение не должно появляться в... аргументе шаблона" (см. Формулировку лямбда-выражения в неоцененном контексте) показалось хорошей идеей. Итак, я написал следующий своего рода рабочий фрагмент, который компилируется на последних gcc и clang:
#include <type_traits>
template<void ( * ) (void) = [](){}> class
unique final {};
static_assert(false == ::std::is_same_v<unique<>, unique<>>);
int main()
{
return 0;
}
Это жизнеспособный подход или один из тех "плохо сформированных, не требующих диагностики" случаев?
Некоторый дополнительный контекст: я хочу использовать это для реализации определений строгого типа в стиле Ады, которые должны работать в одном модуле перевода без ручного изобретения уникальных тегов, которые в противном случае были бы неиспользованными:
struct _tag_WowInt {};
using Int = type<int, _tag_WowInt>;
struct _tag_SoUnique {};
using DifferentInt = type<int, _tag_SoUnique>;
Upd1: Я хотел бы отметить, что подходы, включающие __COUNTER__
или подобные макросы, не будут работать в общем случае, потому что они будут расширены препроцессором только один раз и не будут давать уникальные типы при использовании, например, внутри шаблона.
Ответы
Ответ 1
Это не плохо сформировано, никакой диагностики не требуется. С++ 20, с принятием P0315R4 [expr.prim.lambda]/2 для
Лямбда-выражение - это значение, результат которого называется объектом замыкания. [Примечание: закрывающий объект ведет себя как функциональный объект. - конец примечания]
из старого [expr.prim.lambda]/2
Лямбда-выражение - это значение, результат которого называется объектом замыкания. Лямбда-выражение не должно появляться в неоцененном операнде, в аргументе шаблона, в объявлении псевдонима, в объявлении typedef или в объявлении функции или шаблона функции вне тела функции и аргументов по умолчанию. [Примечание: намерение состоит в том, чтобы не допустить появления лямбд в подписи. - конец примечания] [Примечание: закрывающий объект ведет себя как функциональный объект. - конец примечания]
который требовал, чтобы это была серьезная ошибка.
Похоже, что GCC снова портировал это на -std=c++17
в своем -std=c++17
и clang, кажется, обладает этой функциональностью начиная с версии 5 для -std=c++17
Ответ 2
Я считаю, что вы правы, мне кажется, что это "плохо сформировано, никакой диагностики не требуется". Я думаю, что это покрыто [temp.res/8.4] и [temp.res/8.5]:
(8.4) - гипотетическое создание шаблона сразу после его определения будет некорректным из-за конструкции, которая не зависит от параметра шаблона, или
(8.5) - интерпретация такой конструкции в гипотетической реализации отличается от интерпретации соответствующей конструкции в любой фактической реализации шаблона. [Примечание: это может произойти в следующих ситуациях:
(8.5.1) - тип, используемый в независимом имени, является неполным в момент, когда шаблон определен, но завершен в момент, когда выполняется создание экземпляра, или
(8.5.2) - поиск имени в определении шаблона нашел объявление использования, но поиск в соответствующей области действия в экземпляре не находит никаких объявлений, поскольку объявление использования было расширением пакета, а соответствующий пакет пуст, или же
(8.5.3) - экземпляр использует аргумент по умолчанию или аргумент шаблона по умолчанию, который не был определен в точке, в которой был определен шаблон, или
(8.5.4) - оценка константного выражения в рамках использования экземпляра шаблона
(8.5.4.1) - значение объекта const целочисленного или незаданного типа перечисления или
(8.5.4.2) - значение объекта constexpr или
(8.5.4.3) - значение ссылки или
(8.5.4.4) - определение функции constexpr, и этот объект не был определен при определении шаблона, или
(8.5.5) - специализация шаблона класса или переменная специализация шаблона, которая указывается независимым идентификатором simple-template-id, используется шаблоном, и либо она создается из частичной специализации, которая не была определена, когда шаблон был определено или оно называет явную специализацию, которая не была объявлена при определении шаблона. - конец примечания]
Несмотря на то, что ваш пример использования явно не указан в примерах заметки, в моем понимании требование подразумевает, что unique<>
должен относиться к одной и той же вещи во всей программе, в противном случае он некорректен, никакой диагностики не требуется.
Это был CWG1850. Комитету, похоже, не нравится этот вид метапрограммирования. Счетчик constexpr
больше не работает в новых версиях компиляторов.
Ответ 3
Это решение требует поддержки макроса __COUNTER__
:
template<int> class unique_impl final {};
#define unique unique_impl<__COUNTER__>
static_assert(std::is_same_v<unique, unique> == false);