Passando um conceito para uma função

12

Como os conceitos são definidos como predicados em tempo de compilação, também é possível realmente reutilizá-los para algoritmos em tempo de compilação? Por exemplo, seria possível verificar se todos os tipos em uma tupla estão em conformidade com um conceito? Até onde eu vi, não é possível passar um conceito para uma função de forma alguma, o que me leva a voltar a usar modelos para esses casos.

#include <type_traits>

template<typename T>
concept FloatLike = std::is_same_v<T, float>;

struct IsFloat
{
    template<typename U>
    constexpr static bool test()
    {
       return FloatLike<U>;
    }
};


template<typename Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate::template test<T>() && ...);
}


int main()
{
   static_assert(all_types<IsFloat, float, float>());
   static_assert(!all_types<IsFloat, float, int>());
}

O que eu gostaria de fazer é algo assim, então não preciso envolver o conceito o tempo todo para poder usá-lo:

template<concept Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T> && ...);
}


int main()
{
   static_assert(all_types<FloatLike, float, float>());
   static_assert(!all_types<FloatLike, float, int>());
}

Existe alguma maneira de se aproximar disso?

Andreas Loanjoe
fonte
E então haverá uma proposta para adicionar conceitos de conceitos ... BTW, all_types()pode ser significativamente simplificado usando expressões de dobra ... &&:return (... && Predicate::template test<Ts>());
Evg
@Evg seria ótimo :)
Igor R.

Respostas:

5

Existe alguma maneira de se aproximar disso?

Bem, não, na verdade não. Não em C ++ 20. Atualmente, não há noção na linguagem de um parâmetro-conceito de modelo. Mesmo modelos variáveis ​​não podem ser usados ​​como parâmetros de modelo. Portanto, se você tem um conceito para começar, não podemos evitar o acondicionamento.

Mas o que podemos fazer é escrever invólucros mais simples. Se concordarmos em usar traços do tipo "estilo antigo" como predicados, especificamente aqueles que se comportam como std::integral_constants, podemos ter definições de "conceito" bastante concisas que podem ser usadas como predicados.

template<typename T>
using FloatLike = std::is_same<T, float>;

template<template <typename> class Predicate, typename... T>
constexpr bool all_types()
{
    return (Predicate<T>{} && ...);
}

É tão bom quanto possível , tanto quanto eu posso ver.

Contador de Histórias - Monica Sem Calúnia
fonte
Isso funcionaria descriptografando um lambda genérico como um modelo de modelo? Parece que um lambda nunca é um modelo certo, apenas o operador de chamada?
Andreas Loanjoe 15/11/19
@AndreasLoanjoe - Indeed. Um lambda nunca é um modelo. Mas se você estiver disposto a distribuir lambdas, o C ++ 20 permitirá que você faça isso. Eu posso adicionar uma variante disso em alguns minutos.
StoryTeller - Unslander Monica
@AndreasLoanjoe - Pensando bem, um lambda ainda sai muito detalhado. Não acho que seja uma ótima alternativa. Aqui está de qualquer maneira godbolt.org/z/QSHy8X
StoryTeller - Unslander Monica
Espero que eles adicionem algo melhor :), mas sim, parece que essa é a resposta, apenas os traços de tipo de estilo oferecem esses conceitos de funcionalidade (ainda).
Andreas Loanjoe 15/11/19
0

Se seu objetivo é "verificar se todos os tipos em uma tupla estão em conformidade com um conceito" , você pode fazer algo assim:

// concept to check if all types in Ts are the same as T
template<typename T, typename... Ts>
concept AllSame = (std::is_same_v<T,Ts> && ...);

// function only accepts floats as template parameters
template<AllSame<float>... Floats>
constexpr void float_foo()
{
}

// function only accepts ints as template parameters
template<AllSame<int>... Ints>
constexpr void int_foo()
{
}

// function only accepts T as template parameters
template<typename T, AllSame<T>... Ts>
constexpr void foo()
{
}

int main()
{
    int_foo<int, int, int>();
    // int_foo<int, int, double>(); // fails to compile
    float_foo<float, float, float>();
    // float_foo<float, float, int>(); // fails to compile
    foo<int, int, int, int>();
    // foo<int, int, int, float>(); // fails to compile
    foo<double, double, double, double>();
    // foo<double, double, double, int>(); // fails to compile

}

DEMONSTRAÇÃO AO VIVO

kanstar
fonte
Por que você é AllSamevariável? Cada parâmetro de modelo em um pacote introduzido por uma restrição de tipo já está restrito separadamente.
Davis Herring
@DavisHerring Eu não entendo. Você quer dizer o próprio conceito ou os parâmetros do modelo *_foo()?
kanstar
Quero dizer que o código que você tem funciona se você remover o ...on Tse o && ...que o usa. (Obviamente o nome AllSame, então, seria inapropriado, mas eu não sei por que eu quero expressar uma contagem em unário como <int,int,int>de qualquer maneira.)
Davis Herring
@DavisHerring Então o conceito não seria AllSamemas SameAs(consulte en.cppreference.com/w/cpp/concepts/same_as ) e o OP queria ter um conceito que requer um número variado de parâmetros de modelo.
kanstar
Obviamente seria std::same_as. Eu não acho que a parte variável era o ponto: era a identidade variável (desejada) do conceito. E o que quero dizer é que o aspecto variável do seu exemplo de conceito é irrelevante para o seu uso (porque conceitos não-variáveis ​​também funcionam com pacotes de parâmetros de modelo).
Davis Herring