Como os tipos não especificados podem ser usados em expressões C ++ 20 'requer'?
Estou tentando escrever um conceito C ++ 20 para expressar a exigência de que um tipo tenha um determinado método, que leva um argumento, mas para os fins deste conceito, não me importa qual é o tipo de argumento.
Tentei escrever algo como:
template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
entretanto, gcc e clang rejeitam isso, dando um erro de que 'auto' não pode ser usado na lista de parâmetros de uma expressão de requer desta forma.
Uma alternativa seria colocar o tipo 'x' como um segundo parâmetro do modelo:
template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
{ t.Foo(x) } -> std::same_as<void>;
};
mas então isso requer que TX seja especificado explicitamente sempre que o conceito é usado, ele não pode ser deduzido:
struct S { void Foo(int); };
static_assert(HasFooMethod<S>); // doesn't compile
static_assert(HasFooMethod<S, int>); // the 'int' must be specified
Existe alguma maneira de escrever um conceito que permite a Foo tomar um argumento de tipo não especificado ?
A questão A definição do conceito que exige uma função-membro do modelo restrito é muito semelhante, mas não a mesma: essa questão pergunta como exigir que um método (modelado) possa assumir qualquer tipo que satisfaça um determinado conceito, enquanto esta questão é sobre como exigir que um método tome algum tipo particular, embora esse tipo não seja especificado. Em termos de quantificadores, a outra pergunta é sobre a quantificação universal (limitada), enquanto esta é sobre a quantificação existencial. A resposta da outra pergunta também não se aplica ao meu caso.
Respostas
Os conceitos não têm como objetivo fornecer o tipo de funcionalidade que você está procurando. Então, eles não fornecem isso.
Um conceito destina-se a restringir modelos, para especificar um conjunto de expressões ou instruções que um modelo pretende usar (ou pelo menos ser livre para usar) em sua definição.
No modelo que você está restringindo, se você escrever a expressão t.Foo(x)
, saberá o tipo de x
. É um tipo concreto, um parâmetro de modelo ou um nome derivado de um parâmetro de modelo. De qualquer forma, o tipo de x
está disponível no modelo que está sendo restringido.
Portanto, se você quiser restringir esse modelo, use tanto o tipo de t
quanto o tipo de x
. Ambos estão disponíveis para você naquele momento, portanto, não há problema em criar tal restrição. Ou seja, a restrição não está ativada T
como um tipo isolado; está na associação entre T
e X
.
Os conceitos não devem funcionar no vácuo, sem qualquer associação com o local real de uso da restrição. Você não deve se concentrar na criação de conceitos unários para que os usuários possam static_assert
usar suas classes neles. Os conceitos não servem para testar se um tipo os cumpre (que é basicamente o que você static_assert
está fazendo); eles servem para restringir a definição do modelo que os usa .
Sua restrição precisa ser FooCallableWith
, não HasFooMethod
.
Algo próximo a isso pode ser realizado definindo um tipo de adaptador que pode converter implicitamente para (quase) qualquer coisa:
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&&();
};
Observe que esses operadores não precisam ser implementados, pois eles serão usados apenas em um contexto não avaliado (e, de fato, eles não poderiam ser implementados para todos os tipos T).
Então, HasFooMethod
pode ser escrito como:
template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
{ t.Foo(a) } -> std::same_as<void>;
};