шаблон с лямбдой в качестве уникального параметра по умолчанию для каждого экземпляра

Я ищу способ автоматически сделать параметр шаблона по умолчанию уникальным каждый раз, когда создается экземпляр шаблона. Поскольку объекты безымянных функций, создаваемые лямбда-выражениями, имеют разные типы, я подумал о том, чтобы как-то их принять. В связи с недавними изменениями в стандартном отчуждении, снятие ограничения "лямбда-выражение не должно появляться в... аргументе шаблона" (см. Формулировку лямбда-выражения в неоцененном контексте) показалось хорошей идеей. Итак, я написал следующий своего рода рабочий фрагмент, который компилируется на последних 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);