Como implementar a forma generalizada de std :: same_as (ou seja, para mais de dois parâmetros de tipo) que é independente da ordem dos parâmetros?

8

fundo

Sabemos que o conceito std::same_asé agnóstico em ordem (em outras palavras, simétrico): std::same_as<T, U>é equivalente a std::same_as<U, T>( questão relacionada ). Nesta pergunta, eu gostaria de implementar algo mais geral: template <typename ... Types> concept same_are = ...que verifique se os tipos no pacote Typessão iguais entre si.

Minha tentativa

#include <type_traits>
#include <iostream>
#include <concepts>

template <typename T, typename... Others>
concept same_with_others = (... && std::same_as<T, Others>);

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

template< class T, class U> requires are_same<T, U>
void foo(T a, U b) {
    std::cout << "Not integral" << std::endl;
}

// Note the order <U, T> is intentional
template< class T, class U> requires (are_same<U, T> && std::integral<T>)
void foo(T a, U b) {
    std::cout << "Integral" << std::endl;
}

int main() {
    foo(1, 2);
    return 0;
}

(Minha intenção aqui é enumerar todos os tipos possíveis de tipos ordenados no pacote)

Infelizmente, esse código não seria compilado , com o compilador reclamando que a chamada para foo(int, int)é ambígua. Acredito que considere are_same<U, T>e are_same<T, U>como não equivalente. Gostaria de saber por que o código falha, como posso corrigi-lo (para que o compilador os trate como equivalentes)?

Rin Kaenbyou
fonte
Meu intestino me diz que vai precisar de um ajudante que seja executado same_with_othersem todas as permutações possíveis dos tipos.
StoryTeller - Unslander Monica
Não tenho muita certeza se entendi corretamente. Deseja verificar se todos os tipos de ... Typessão iguais? Talvez std :: joint possa ajudá-lo. Há um exemplo na parte inferior da página que parece semelhante à sua abordagem.
churill
@ StoryTeller-UnslanderMonica Mas eu já enumerei todos os tipos possíveis de tipos ordenados no pacote. Isso não é suficiente? Ou os compiladores não podem determinar a equivalência de dobras sem nenhum tipo de concreto?
Rin Kaenbyou
@churill Pretendo implementar isso em conceitos, e a ordenação de parâmetros precisa de cuidados especiais em conceitos.
Rin Kaenbyou
Não tenho certeza, portanto, é apenas um pressentimento. Pode ser que os desenvolvedores do GCC também não tenham certeza. Também pode ser que ainda não o tenham implementado completamente.
StoryTeller - Unslander Monica

Respostas:

5

O problema é que, com este conceito:

template <typename T, typename... Others>
concept are_same = (... && std::same_as<T, Others>);

É que a forma normalizada desse conceito é ... exatamente isso. Não podemos "desdobrar" isso (não há nada a fazer), e as regras atuais não se normalizam através das "partes" de um conceito.

Em outras palavras, o que você precisa para que isso funcione é que seu conceito se normalize em:

... && (same-as-impl<T, U> && same-as-impl<U, T>)

para dentro:

... && (is_same_v<T, U> && is_same_v<U, T>)

E considere uma &&restrição de expressão de dobra para incluir outra restrição de expressão de dobra &&se sua restrição subjacente subsumir a restrição subjacente da outra. Se tivéssemos essa regra, isso faria o seu exemplo funcionar.

Pode ser possível adicionar isso no futuro - mas a preocupação com as regras de subsunção é que não queremos exigir que os compiladores façam tudo e implementem um solucionador SAT completo para verificar a subsunção de restrição. Este não parece que o torna muito mais complicado (nós realmente adicionamos as regras &&e ||através de expressões de dobra), mas eu realmente não tenho ideia.

Observe, no entanto, que mesmo se tivéssemos esse tipo de subsunção de expressão de dobra, are_same<T, U>ainda assim não haveria subsunção std::same_as<T, U>. Seria apenas subsumir are_same<U, T>. Não tenho certeza se isso seria possível.

Barry
fonte
2
É surpreendente para mim que isso não funcione. É razoável que apenas conceitos possam ser incluídos. No entanto, na minha opinião, (... && C<T>)não incluir o conceito C<T> surpreenderia muitos usuários.
metalfox
@metalfox: Na minha leitura da normalização, seu exemplo deve ser bom (usar restrições funciona explicitamente no Demo ).
Jarod42
@ Jarod42 O que você escreveu e o que o metalfox escreveu não é a mesma coisa - a diferença é do que o metalfox está falando.
Barry
@ Jarod42 Sim, isso funciona porque os conceitos participam da subsunção de restrição e você materializou a expressão de dobra (envolvendo conceitos) em um único conceito. Infelizmente, como você afirmou em sua resposta, as expressões de dobra não são normalizadas nos conceitos de que são feitas. Isso também não funciona: godbolt.org/z/pjmKxR
metalfox 12/11/19
Eu poderia ter entendido mal Constraint_normalization então: - / Eu entendo ((fold1<Ts> && ...) && (fold2<Ts> &&...))como conjunto de (fold1<Ts> && ...)e (fold2<Ts> && ...)considerando que é atômica.
Jarod42
5

De cppreference.com Normalização de restrições

A forma normal de qualquer outra expressão E é a restrição atômica cuja expressão é E e cujo mapeamento de parâmetro é o mapeamento de identidade. Isso inclui todas as expressões de dobra, mesmo aquelas que se dobram sobre && ou || operadores.

assim

template <typename... Types>
concept are_same = (... && same_with_others<Types, Types...>);

é "atômico".

Então, de fato are_same<U, T>e are_same<T, U>não são equivalentes.

Não vejo como implementá-lo :-(

Jarod42
fonte