Как можно использовать неуказанные типы в выражениях "требует" C ++ 20?
Я пытаюсь написать концепцию C ++ 20, чтобы выразить требование, чтобы у типа был определенный метод, который принимает аргумент, но для целей этой концепции меня не волнует тип аргумента.
Я пробовал написать что-то вроде:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
однако и gcc, и clang отклоняют это, выдавая ошибку, заключающуюся в том, что 'auto' не может использоваться таким образом в списке параметров выражения requires.
Альтернативой было бы указать тип 'x' в качестве второго параметра шаблона:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
но тогда это требует, чтобы TX был явно указан всякий раз, когда используется концепция, это не может быть выведено:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
Есть ли способ написать концепцию, позволяющую Foo принимать аргумент неопределенного типа?
Определение концепции вопроса, требующее ограниченной функции-члена шаблона , очень похоже, но не то же самое: в этом вопросе задается вопрос, как требовать, чтобы (шаблонный) метод мог принимать любой тип, удовлетворяющий данной концепции, в то время как этот вопрос касается требования, чтобы метод принимал какой-то конкретный тип, хотя этот тип не указан. Что касается кванторов, другой вопрос касается (ограниченной) универсальной квантификации, в то время как этот вопрос касается квантификации существования. Ответ на другой вопрос также не относится к моему случаю.
Ответы
Концепции не предназначены для предоставления той функциональности, которую вы ищете. Так что они этого не предоставляют.
Концепция предназначена для ограничения шаблонов, чтобы указать набор выражений или операторов, которые шаблон намеревается использовать (или, по крайней мере, может свободно использовать) в своем определении.
Если вы напишете выражение в шаблоне, который вы так ограничиваете t.Foo(x)
, то вы знаете его тип x
. Это либо конкретный тип, либо параметр шаблона, либо имя, производное от параметра шаблона. В любом случае тип x
доступен в ограничиваемом шаблоне.
Поэтому, если вы хотите ограничить такой шаблон, вы можете использовать как тип, так t
и тип x
. Оба варианта доступны вам в это время, поэтому нет проблем с созданием такого ограничения. То есть ограничение не T
как изолированный тип; это связано с ассоциацией между T
и X
.
Концепции не предназначены для работы в вакууме, лишенном какой-либо связи с фактическим местом использования ограничения. Вы не должны сосредотачиваться на создании унарных концепций, чтобы пользователи могли static_assert
противопоставить свои классы им. Концепции не предназначены для тестирования, соответствует ли тип им (что в основном то, что вы static_assert
делаете); они предназначены для ограничения определения шаблона, который их использует .
Ваше ограничение должно быть FooCallableWith
, а не HasFooMethod
.
Что-то близкое к этому может быть достигнуто путем определения типа адаптера, который может неявно преобразовывать (почти) во что угодно:
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&&();
};
Обратите внимание, что эти операторы не нужно реализовывать, поскольку они будут использоваться только в неоцененном контексте (и действительно, они не могут быть реализованы для всех типов T).
Тогда HasFooMethod
это можно записать как:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};