Come possono essere usati tipi non specificati nelle espressioni "richiede" di C ++ 20?

Aug 16 2020

Sto cercando di scrivere un concetto C ++ 20 per esprimere il requisito che un tipo abbia un certo metodo, che accetta un argomento, ma ai fini di questo concetto non mi interessa quale sia il tipo di argomento.

Ho provato a scrivere qualcosa del tipo:

template <typename T>
concept HasFooMethod = requires(T t, auto x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

tuttavia, sia gcc che clang lo rifiutano, dando un errore che 'auto' non può essere usato in questo modo nell'elenco dei parametri di un'espressione require.

Un'alternativa sarebbe inserire il tipo di "x" come secondo parametro del modello:

template <typename T, typename TX>
concept HasFooMethod = requires(T t, TX x)
{
    { t.Foo(x) } -> std::same_as<void>;
};

ma poi questo richiede che TX sia specificato esplicitamente ogni volta che il concetto viene utilizzato, non può essere dedotto:

struct S { void Foo(int); };
static_assert(HasFooMethod<S>);         // doesn't compile
static_assert(HasFooMethod<S, int>);    // the 'int' must be specified

C'è un modo per scrivere un concetto che consenta a Foo di accettare un argomento di tipo non specificato ?

La domanda Definizione del concetto che richiede una funzione membro del modello vincolata è molto simile, ma non la stessa: quella domanda chiede come richiedere che un metodo (basato su modelli) possa assumere qualsiasi tipo soddisfacente un dato concetto, mentre questa domanda riguarda la richiesta che un metodo prenda un tipo particolare, sebbene quel tipo non sia specificato. In termini di quantificatori, l'altra domanda riguarda la quantificazione universale (limitata) mentre questa riguarda la quantificazione esistenziale. Anche la risposta dell'altra domanda non si applica al mio caso.

Risposte

4 NicolBolas Aug 17 2020 at 13:48

I concetti non hanno lo scopo di fornire il tipo di funzionalità che stai cercando. Quindi non lo forniscono.

Un concetto ha lo scopo di limitare i modelli, per specificare un insieme di espressioni o istruzioni che un modello intende utilizzare (o almeno essere libero di usare) nella sua definizione.

All'interno del modello che stai così vincolando, se scrivi l'espressione t.Foo(x), allora conosci il tipo di x. È un tipo concreto, un parametro del modello o un nome derivato da un parametro del modello. In ogni caso, il tipo di xè disponibile nel modello vincolato.

Quindi, se vuoi vincolare un modello di questo tipo, usi sia il tipo di tche il tipo di x. Entrambi sono disponibili in quel momento, quindi non ci sono problemi con la creazione di un tale vincolo. Cioè, il vincolo non è attivo Tcome tipo isolato; è sull'associazione tra Te X.

I concetti non sono pensati per funzionare nel vuoto, privi di qualsiasi associazione con il luogo di utilizzo effettivo del vincolo. Non dovresti concentrarti sulla creazione di concetti unari in modo che gli utenti possano static_assertsfidarli con le loro classi. I concetti non sono pensati per verificare se un tipo li soddisfa (che è fondamentalmente ciò che stai static_assertfacendo); hanno lo scopo di limitare la definizione del modello che li utilizza .

Il tuo vincolo deve essere FooCallableWith, no HasFooMethod.

NathanReed Aug 17 2020 at 16:30

Qualcosa di simile a questo può essere ottenuto definendo un tipo di adattatore che può convertire implicitamente in (quasi) qualsiasi cosa:

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&&();
};

Si noti che questi operatori non devono essere implementati, poiché verranno utilizzati solo in un contesto non valutato (e in effetti, non potrebbero essere implementati per tutti i tipi T).

Quindi, HasFooMethodpuò essere scritto come:

template <typename T>
concept HasFooMethod = requires(T t, anything a)
{
    { t.Foo(a) } -> std::same_as<void>;
};