Jak można używać nieokreślonych typów w wyrażeniach „wymaga” języka C ++ 20?
Próbuję napisać koncepcję C ++ 20, aby wyrazić wymaganie, aby typ miał określoną metodę, która przyjmuje argument, ale dla celów tej koncepcji nie obchodzi mnie, jaki jest typ argumentu.
Próbowałem napisać coś takiego:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
jednakże zarówno gcc, jak i clang odrzucają to, dając błąd, że „auto” nie może być użyte w ten sposób na liście parametrów wyrażenia wymaganego.
Alternatywą byłoby umieszczenie typu „x” jako drugiego parametru szablonu:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
ale wtedy wymaga to wyraźnego określenia TX za każdym razem, gdy używane jest pojęcie, nie można go wydedukować:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
Czy istnieje sposób na napisanie koncepcji, która pozwoli Foo wziąć argument nieokreślonego typu?
Pytanie Definicja pojęcia wymagająca ograniczonej funkcji składowej szablonu jest bardzo podobna, ale nie to samo: to pytanie dotyczy tego, jak wymagać, aby metoda (oparta na szablonie) mogła przyjmować dowolny typ spełniający daną koncepcję, podczas gdy to pytanie dotyczy wymagania, aby metoda wymagała jakiś szczególny typ, chociaż ten typ jest nieokreślony. Jeśli chodzi o kwantyfikatory, drugie pytanie dotyczy (ograniczonej) uniwersalnej kwantyfikacji, podczas gdy ta dotyczy kwantyfikacji egzystencjalnej. Odpowiedź na drugie pytanie również nie ma zastosowania w moim przypadku.
Odpowiedzi
Koncepcje nie mają na celu zapewnienia funkcjonalności, której szukasz. Więc tego nie zapewniają.
Pojęcie ma na celu ograniczenie szablonów, określenie zestawu wyrażeń lub instrukcji, których szablon zamierza używać (lub przynajmniej być swobodnie używany) w swojej definicji.
W szablonie, który tak ograniczasz, jeśli napiszesz wyrażenie t.Foo(x)
, znasz jego typ x
. Jest to konkretny typ, parametr szablonu lub nazwa wyprowadzona z parametru szablonu. Tak czy inaczej, typ x
jest dostępny w ograniczanym szablonie.
Jeśli więc chcesz ograniczyć taki szablon, użyj zarówno typu, jak t
i typu x
. Oba są wtedy dostępne dla Ciebie, więc nie ma problemu z utworzeniem takiego ograniczenia. Oznacza to, że ograniczenie nie jest włączone T
jako typ izolowany; jest na powiązaniu między T
a X
.
Koncepcje nie mają działać w próżni, pozbawione jakiegokolwiek związku z rzeczywistym miejscem użycia ograniczenia. Nie powinieneś skupiać się na tworzeniu jednoargumentowych koncepcji, aby użytkownicy mogli static_assert
używać swoich klas przeciwko nim. Pojęcia nie są przeznaczone do testowania, jeśli typ je spełnia (czyli w zasadzie to, co static_assert
robisz); służą do ograniczania definicji szablonu, która ich używa .
Twoje ograniczenie musi być FooCallableWith
, a nie HasFooMethod
.
Coś bliskiego można osiągnąć, definiując typ adaptera, który może niejawnie konwertować na (prawie) wszystko:
struct anything
{
// having both these conversions allows Foo's argument to be either
// a value, an lvalue reference, or an rvalue reference
template <typename T>
operator T&();
template <typename T>
operator T&&();
};
Zauważ, że te operatory nie muszą być implementowane, ponieważ będą używane tylko w niedocenianym kontekście (i rzeczywiście, nie mogą być zaimplementowane dla wszystkich typów T).
Następnie HasFooMethod
można zapisać jako:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};