Как можно использовать неуказанные типы в выражениях "требует" C ++ 20?

Aug 16 2020

Я пытаюсь написать концепцию 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 принимать аргумент неопределенного типа?

Определение концепции вопроса, требующее ограниченной функции-члена шаблона , очень похоже, но не то же самое: в этом вопросе задается вопрос, как требовать, чтобы (шаблонный) метод мог принимать любой тип, удовлетворяющий данной концепции, в то время как этот вопрос касается требования, чтобы метод принимал какой-то конкретный тип, хотя этот тип не указан. Что касается кванторов, другой вопрос касается (ограниченной) универсальной квантификации, в то время как этот вопрос касается квантификации существования. Ответ на другой вопрос также не относится к моему случаю.

Ответы

4 NicolBolas Aug 17 2020 at 13:48

Концепции не предназначены для предоставления той функциональности, которую вы ищете. Так что они этого не предоставляют.

Концепция предназначена для ограничения шаблонов, чтобы указать набор выражений или операторов, которые шаблон намеревается использовать (или, по крайней мере, может свободно использовать) в своем определении.

Если вы напишете выражение в шаблоне, который вы так ограничиваете t.Foo(x), то вы знаете его тип x. Это либо конкретный тип, либо параметр шаблона, либо имя, производное от параметра шаблона. В любом случае тип xдоступен в ограничиваемом шаблоне.

Поэтому, если вы хотите ограничить такой шаблон, вы можете использовать как тип, так tи тип x. Оба варианта доступны вам в это время, поэтому нет проблем с созданием такого ограничения. То есть ограничение не Tкак изолированный тип; это связано с ассоциацией между Tи X.

Концепции не предназначены для работы в вакууме, лишенном какой-либо связи с фактическим местом использования ограничения. Вы не должны сосредотачиваться на создании унарных концепций, чтобы пользователи могли static_assertпротивопоставить свои классы им. Концепции не предназначены для тестирования, соответствует ли тип им (что в основном то, что вы static_assertделаете); они предназначены для ограничения определения шаблона, который их использует .

Ваше ограничение должно быть FooCallableWith, а не HasFooMethod.

NathanReed Aug 17 2020 at 16:30

Что-то близкое к этому может быть достигнуто путем определения типа адаптера, который может неявно преобразовывать (почти) во что угодно:

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>;
};