Pergunta

Durante a leitura de uma outra pergunta, eu vim para um problema com a ordem parcial, que i cortadas para o teste caso seguinte

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

int main() {
  // GCC chokes on f(0, 0) (not being able to match against T1)
  void *p = 0;
  f(0, p);
}

Para ambos os modelos de função, o tipo de função da especialização que entra resolução de sobrecarga é void(int, void*). Mas ordenação parcial (de acordo com comeau e GCC) agora diz que o segundo modelo é mais especializada. Mas por quê?

Deixe-me passar por ordenação parcial e mostrar onde eu tenho perguntas. Pode Q ser um tipo fez-up exclusivo usado para determinar ordenação parcial de acordo com 14.5.5.2.

  • Transformado parâmetro-lista para T1 (Q inserido): (Q, typename Const<Q>::type*). Os tipos dos argumentos são AT = (Q, void*)
  • Transformado parâmetro-lista para T2 (Q inserido): BT = (Q, void*), que também são os tipos dos argumentos.
  • lista de parâmetros não-transformados para T1: (T, typename Const<T>::type*)
  • lista de parâmetros não-transformados para T2: (T, void*)

Desde C ++ 03 sub-especifica isso, eu uso a intenção de que eu li sobre em vários relatórios de defeitos. A lista de parâmetros acima transformado para T1 (chamado AT por mim) é usado como lista de argumentos para 14.8.2.1 "deduzir argumentos de modelo de uma chamada de função" .

14.8.2.1 não precisa transformar AT ou BT-se mais (como, removendo declarators de referência, etc), e vai directamente para 14.8.2.4, que de forma independente para cada par A / P faz tipo dedução:

  • AT contra T2: { (Q, T) , (void*, void*) } . T é o único parâmetro de modelo aqui, e ele vai achar que T deve ser Q. Tipo de dedução sucede trivialmente para AT contra T2.

  • BT contra T1: { (Q, T) , (void*, typename Const<T>::type*) } . Ele vai achar que T é Q também aqui. typename Const<T>::type* é um contexto un-deduzida, e por isso não vai ser usado para qualquer coisa deduzir.


Aqui é a minha primeira pergunta: Será que isto vai agora usar o valor de T deduzida para o primeiro parâmetro? Se a resposta é não, então o primeiro modelo é mais especializada. Este não pode ser o caso, porque tanto GCC e Comeau dizer que o segundo modelo é mais especializada, e eu não acredito que eles estão errados. Então, nós assumimos "sim", e inserção void* em T. O parágrafo (14.8.2.4) diz "dedução é feito de forma independente para cada par e os resultados são então combinados" e também "Em certos contextos, no entanto, o valor não participa tipo de dedução, mas em vez disso usa os valores de argumentos de modelo que foram ou deduzidas em outro lugar ou explicitamente especificadas. " isso soa como 'sim' também.

dedução por conseguinte sucede também, para cada par de A / P. Agora, cada modelo é pelo menos tão especializada como o outro, porque dedução também não depende de quaisquer conversões implícitas e conseguiu em ambas as direções. Como resultado, a chamada deve ser ambíguo.

Assim, a minha segunda pergunta: Agora, por que as implementações dizer que o segundo modelo é mais especializada? O ponto é que eu esquecer?


Editar : Eu testei especialização explícita e instanciação, e ambos, nas versões recentes do CCG (4.4) me dizem que a referência aoespecialização é ambígua, enquanto uma versão mais antiga do GCC (4.1) não subir esse erro ambigüidade. Isto sugere que as versões recentes do CCG têm ordenação parcial inconsistente para modelos de função.

template<typename T>
struct Const { typedef void type; };

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

template<> void f(int, void*) { }
  // main.cpp:11: error: ambiguous template specialization 
  // 'f<>' for 'void f(int, void*)'
Foi útil?

Solução

Aqui está a minha ir a este. Concordo com Charles Bailey que a incorreta passo é ir de Const<Q>::Type* para void*

template<typename T>
void f(T, typename Const<T>::type*) { cout << "Const"; } // T1

template<typename T>
void f(T, void*) { cout << "void*"; } // T2

Os passos que pretende tomar são:

14.5.5.2/2

Dado dois modelos de função sobrecarregados, se a pessoa é mais especializado do que o outro pode ser determinada pela transformando cada modelo, por sua vez e usando dedução argumento (14.8.2) para compará-lo com o outro.

14.5.5.2/3-b1

Para cada tipo de parâmetro modelo, sintetizar um tipo único e substituto que, para cada ocorrência desse parâmetro na lista de parâmetros de função, ou para uma função de conversão de modelo, no tipo de retorno.

Na minha opinião, os tipos são sintetizados da seguinte forma:

(Q, Const<Q>::Type*)    // Q1
(Q, void*)              // Q2

Eu não vejo qualquer texto que requer que o parâmetro segundo sintetizado de T1 ser void*. Eu não sei de qualquer precedente para que, em outros contextos também. O tipo Const<Q>::Type* é tipo perfeitamente válida dentro do C ++ sistema de tipo.

Então, agora nós executar as etapas de dedução:

Q2 para T1

Nós tentar deduzir os parâmetros do modelo para T1 por isso temos:

  • Parâmetro 1: T é deduzida a ser Q
  • Parâmetro 2: contexto Nondeduced

Apesar de parâmetro 2 é um contexto não deduzida, a dedução tem ainda conseguiu porque temos um valor para T.

Q1 para T2

Deduzir os parâmetros do modelo para T2 temos:

  • Parâmetro 1: T é deduzida a ser Q
  • Parâmetro 2:. void* não corresponde Const<Q>::Type* tão falha dedução

IMHO, é aqui que o padrão permite. O parâmetro não é dependente de modo que não está muito claro o que deve acontecer, no entanto, a minha experiência (com base em uma leitura squinted de 14.8.2.1/3) é que, mesmo quando o tipo de parâmetro P não é dependente, em seguida, o tipo de argumento A deve corresponder -lo.

Os argumentos sintetizados de T1 pode ser utilizado para se especializam T2, mas não vice-versa. T2 é, portanto, mais especializado do que T1 e por isso é a melhor função.


UPDATE 1:

Apenas para cobrir o poing sobre Const<Q>::type estar vazio. Considere o seguinte exemplo:

template<typename T>
struct Const;

template<typename T>
void f(T, typename Const<T>::type*) // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T>
void f(T, void*)                    // T2
{ typedef typename T::TYPE2 TYPE ; }

template<>
struct Const <int>
{
  typedef void type;
};

template<>
struct Const <long>
{
  typedef long type;
};

void bar ()
{
  void * p = 0;
  f (0, p);
}

No exemplo acima, Const<int>::type é usado quando estamos executando as regras habituais de resolução de sobrecarga, mas não quando nós começamos com as regras de sobrecarga parciais. Não seria correto de escolher uma especialização arbitrário para Const<Q>::type. Pode não ser intuitivo, mas o compilador é bastante feliz por ter um tipo synthasized do Const<Q>::type* formulário e usá-lo durante o tipo de dedução.


UPDATE 2

template <typename T, int I>
class Const
{
public:
  typedef typename Const<T, I-1>::type type;
};

template <typename T>
class Const <T, 0>
{
public:
  typedef void type;
};

template<typename T, int I>
void f(T (&)[I], typename Const<T, I>::type*)     // T1
{ typedef typename T::TYPE1 TYPE; }

template<typename T, int I>
void f(T (&)[I], void*)                           // T2
{ typedef typename T::TYPE2 TYPE ; }


void bar ()
{
  int array[10];
  void * p = 0;
  f (array, p);
}

Quando o modelo Const é instanciado com algum valor I, ele recursivamente instancia-se até I atinge 0. Isto é, quando o Const<T,0> especialização parcial é selecionado. Se temos um compilador que sintetiza algum tipo real para os parâmetros da função, então o valor será o compilador escolher para o índice da matriz? Digamos que 10? Bem, isso seria bom para o exemplo acima, mas que não iria coincidir com o Const<T, 10 + 1> especialização parcial que, conceitualmente, pelo menos, resultaria em um número infinito de instantiations recursiva do primário. Seja qual for o valor que ele selecionou poderíamos modificar a condição final para ser esse valor + 1, e então teríamos um loop infinito no algoritmo de ordenação parcial.

Eu não vejo como o algoritmo de ordenação parcial poderia Const corretamente instanciar para encontrar what type realmente é.

Outras dicas

Edit: Depois de estudar implementação do Clang (por Doug Gregor) de seu algoritmo de ordenação parcial, eu vim para Concordo com o resto dos cartazes que o exemplo original não é 'destinados' a ser ambígua - apesar da norma não é tão claro quanto poderia ser sobre o que deve acontecer em tais situações. Eu editei este post para indicar meus pensamentos revistos (para o meu próprio benefício e de referência). Em particular o algoritmo de Clang esclareceu que 'typename Const<T>::type' não se traduz em 'vazio' durante a etapa de ordenação parcial -. E que cada par A / P é independente deduzida do outro

Inicialmente eu me perguntava por que o seguinte texto foi considerado ambíguo:

        template<class T> void f(T,T*);  // 1

        template<class T> void f(T, int*); // 2

        f(0, (int*)0); // ambiguous

(The above is ambiguous because one cannot deduce f1(U1,U1*) from f2(T,int*), and going the other way, one cannot deduce f2(U2,int*) from f1(T,T*). Neither is more specialized.)

Mas o seguinte não seria ambígua:

        template<class T> struct X { typedef int type; };
        template<class T> void f(T, typename X<T>::type*); // 3
        template<class T> void f(T, int*); // 2

(A razão se poderia esperar que ele seja ambígua é se o seguinte vier a acontecer:
- f3(U1,X<U1>::type*) -> f3(U1, int*) ==> f2(T,int*) (deduction ok, T=U1)
- f2(U2,int*) ==> f3(T, X<T>::type*) (deduction ok, T=U2 makes X<U2>::type* -> int*)
Se isso fosse verdade, nenhum deles seria mais especializada do que o outro.)

Depois de estudar algoritmo de ordenação parcial do Clang é claro que eles tratam '3' acima como se fosse:

template<class T, class S> void f(T, S*); // 4

para dedução de algum único 'U' contra 'typename X :: tipo' terá sucesso -

  • f3(U1,X<U1>::type*) is treated as f3(U1, U2*) ==> f2(T,int*) (deduction not ok)
  • f2(U2,int*) ==> f3(T,S* [[X<T>::type*]]) (deduction ok, T=U2, S=int)

E assim '2' é claramente mais especializado do que '3'.

lista de parâmetros Transformado em T1 (Q inserido): (Q, NomeDoTipo Const :: tipo *). Os tipos do argumentos são AT = (Q, void *)

Gostaria de saber se isso é realmente uma simplificação correta. Quando você sintetizar o tipo Q, você está autorizado a evocar uma especialização para Const para efeitos de determinação da ordenação de specliazation modelo?

template <>
struct Const<Q> { typedef int type; }

Isto implicaria que T2 não é pelo menos tão especializada como T1 porque um parâmetro void* não corresponder segundo parâmetro de T1 para quaisquer parâmetros determinado modelo.

Edit: Por favor desconsidere este post - Depois de estudar clangs algoritmo para ordenação parcial como implementado por Doug Gregor (mesmo que seja apenas parcialmente implementado como esta escrito - parece que a lógica que é relevante para a pergunta do OP é implementado de forma cabal ) - ele aparece como se trata o contexto undeduced como apenas mais um parâmetro do modelo. O que sugere que a sobrecarga com o argumento * vazio explícita deve ser a versão mais especializada e não deve haver nenhuma ambigüidade. Como de costume Comeau está correto. Agora, como para a formulação do padrão que define claramente esse comportamento - isso é outro assunto ...

Uma vez que este post também foi postada no comp.lang.c ++ moderada, e parece estar a causar alguma confusão lá também -. Eu pensei que eu ia postar minha resposta a esse grupo aqui também - uma vez que a discussão é obviamente relevante para a pergunta feita aqui.

On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:

You are going one step too fast here. How do you know (and would the compiler know) that there is no specialisation of Const<Q> such that Const<Q>::type != void?

As far as I can see, the compiler would transform the parameter-list of A to: AT=(Q, <unknown>*). To call B with these parameters requires an implicit conversion (<unknown>* to void*) and therefore A is less specialised than B.

Eu acredito que isso é incorreto. Ao verificar para ver qual função é mais especializada (durante parcial-ordenação), o compilador transforma o parâmetro da lista para (Q, void*) - ou seja, que realmente instancia o relevante modelo (melhor correspondência) e olha dentro dela para o valor de 'tipo' - neste caso, com base no modelo principal, será void *.

A respeito de sua ponto relativo especialização parcial - durante a verificação de qual modelo é mais especializado do que o outro, o único tipo que pode ser usado é o tipo gerado única - se há outras especializações no ponto de instanciação da declaração (quando a resolução de sobrecarga está sendo feito) eles serão considerados. Se você adicioná-los mais tarde, e eles devem ser selecionado você estará violando o ODR (de acordo com 14.7.4.1)

As especializações parciais / explícita também irá receber considertaion durante formação do conjunto candidato - mas desta vez usando os tipos de argumentos reais para a função. Se a especialização parcial melhor correspondência (de X) resulta numa Tipo de função que tem uma sequência de conversão melhor implícita para alguns parâmetro, então nós nunca fazê-lo para a fase de ordenação parcial, e que função "melhor" será selecionado (antes de fazer para o parcial fase de ordenação)

Aqui está um exemplo com comentários sobre o que deve estar acontecendo em várias etapas:

    template<class T, bool=true> struct X;  // Primary

    template<class T> struct X<T,true> { typedef T type; };  // A
    template<> struct X<int*,true> { typedef void* type; };  // B


    template<class T> void f(T,typename X<T>::type); //1
    template<class T> void f(T*,void*); //2


    int main()
    {
      void* pv;
      int* pi;


      f(pi,pi);   
      // two candidate functions: f1<int*>(int*,void*),  f2<int>(int*,void*)
      // Note: specialization 'B' used to arrive at void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1,true>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity 




      f(pv,pv);  
      // two candidate functions: f1<void*>(void*,void*), f2<void>(void*,void*)
      // Note: specialization 'A' used to arrive at second void* in f1
      // neither has a better ICS than the other, so lets partially order
      // transformed f1 is f1<U1>(U1,X<U1>::type) --> f1<U1>(U1,U1) 
      //       (template 'A' used to get the second U1)
      // obviously deduction will fail (U1,U1) -> (T*,void*)
      // and also fails the other way (U2*, void*) -> (T,X<T>::type)
      // can not partially order them - so ambiguity again             

    }

Também vale a pena mencionar que se o modelo primário não ter uma definição - então SFINAE opera durante a fase de ordenação parcial, nem pode ser deduzida a partir do outro, e ambiguidade deve resultar.

Além disso, se você adicionar outro modelo que levaria a outro jogo, se o ponto de instantation de qualquer uma dessas funções é movido para outro local na unidade de tradução que você vai violar claramente a ODR.

On Jul 25, 1:11 pm, Bart van Ingen Schenau <b...@ingen.ddns.info> wrote:

Em primeiro lugar, sendo mais meios especializados que estes são menos tipos onde esse modelo pode ser seleccionado por resolução de sobrecarga. Usando isso, as regras para a ordenação parcial pode ser resumido como: Tente encontrar um tipo de A tal que A pode ser chamado, mas B não, ou sobrecarga resolução prefere chamar A. Se esse tipo pode ser encontrado, então B é mais especializada do que A.

Nenhum argumento aqui. Mas com base nas regras como elas são atualmente, o exemplo do OP tem que ser ambígua.


Finalmente, aqui está explícito, respostas inequívocas para as duas questões específicas levantadas por litb:

1) Será que isto vai agora usar o valor de T deduzida para o primeiro parâmetro?
Sim - é claro, tem que, ele está fazendo argumento modelo de dedução - os 'links' tem que ser mantida.

2) Agora, por que as implementações dizer que a segunda é mais especializado em vez disso?
Porque eles estão errados;)

Espero que isso coloca a questão para descansar - Por favor, deixe-me know se há alguma coisa que ainda não está claro:)

Edit: litb levantou um bom ponto em seu comentário - talvez afirmando que o modelo principal sempre vai chegar utilizado para a instanciação com o tipo gerado única é muito forte um comunicado.
Há casos em que o modelo primário não será chamado.
O que estou querendo dizer é que quando ordenação parcial está ocorrendo, algum tipo gerado único é usado para combinar o melhor especialização. Você está certo, ele não tem que ser o modelo primário. Eu editei a linguagem acima para fazê-lo. Ele também levantou uma questão sobre a definição de um melhor modelo de correspondência após o ponto de instantation. Isso vai ser uma violação da ODR de acordo com a seção sobre ponto de instanciação.


A norma diz que uma vez que os pares A / P são criados (usando as regras de transformação, como descrito no temp.func.order) são deduzidas contra o outro usando dedução argumento modelo (temp.deduct) - e que lida com seção o caso de contextos não-deduzidos, instanciar o modelo e seu tipo nested, provocando pontos de instâncias. A seção temp.point lida com as violações ODR (o sentido da ordenação parcial não deve mudar, independentemente dos pontos de instantation dentro de uma unidade de tradução). Eu ainda não tenho certeza de onde a confusão é que vem? - Faisal Vali 1 hora atrás [excluir este comentário]

litb: "Note-se que o passo que coloca Q em Const :: tipo para construir os argumentos não são cobertos explicitamente pela regra SFINAE. O SFINAE regras de trabalho com dedução argumento, os parágrafos que colocam Q na lista de parâmetros função de modelo de função estão em 14.5.5.2. '

As regras SFINAE tem que ser usado aqui - como poderiam não ser? Eu sinto que é suficientemente implícita - Eu não vou negar que ele poderia ser mais claro, e enquanto eu incentivar a comissão para esclarecer isso -. Eu não acho que isso precisa ser esclarecido para interpretar o seu exemplo suficientemente

Deixe-me fornecer uma maneira de ligá-los. De (14.8.2): "Quando uma lista de argumentos modelo explícita for especificado, os argumentos de modelo deve ser compatível com o lista de parâmetros do modelo e deve resultar em um tipo de função válido conforme descrito abaixo; dedução de outra forma tipo falhar "

De (14.5.5.2/3) "A transformação utilizada é: - Para cada tipo de parâmetro modelo, sintetizar um tipo único e substituto que, para cada ocorrência de esse parâmetro na lista de parâmetros de função, ou para uma função de conversão de modelo, no tipo de retorno. "

Em minha mente, a citação acima implica que uma vez que você "criar" tipos gerados exclusivos para cada parâmetro do modelo, a declaração de função tem de estar implicitamente instanciado por explicitamente fornecendo os únicos tipos como argumentos de modelo para o nosso modelo de função. Se isto resultar em um inválido tipo de função, então não só a transformação, mas mais importante do posterior modelo argumento dedução necessário encomendar parcialmente a função falha.

De (14.5.5.2/4) "Usando a lista de parâmetros função transformado, execute argumento dedução contra o outro modelo de função. O modelo transformado é pelo menos tão especializada como o outro se, e somente se , a dedução bem-sucedida e os tipos de parâmetro deduzidos são uma correspondência exata (para que a dedução não depende de conversões implícitas). "

Se a função Lista de parâmetros leads transformados à falha de substituição, então sabemos dedução poderia não conseguiram. E desde que a dedução não teve sucesso, ele não é tão especializada como o outro - isso é tudo o que precisamos saber para prosseguir na ordenação parcial dos dois.

litb: Eu também não sei o que acontece neste caso: template<typename T> struct A; template<typename T> void f(T, typename A<T>::type); template<typename T> void f(T*, typename A<T>::type); certamente, que está indended ser um código válido, mas fazê-A :: tipo, ele irá falhar porque no contexto definição molde, A não está ainda definida" Observe também que não há POI definido para instâncias de modelo resultantes desta tipo de substituição durante a tentativa de determinar uma ordenação (ordenação parcial não depende em qualquer contexto. É uma propriedade estática de dois modelos de função envolvidas). Eu acho que isto parece um problema no padrão que precisa ser corrigido.

Ok - eu penso que eu ver onde nós estamos vendo as coisas de forma diferente. Se eu entendi corretamente, você está dizendo que como esses modelos de função se declarou, o compilador é manter uma faixa da ordem parcial entre eles, independentemente da resolução de sobrecarga nunca ficar acionado para selecionar entre eles. Se é assim que você interpretá-lo, então eu posso ver por que você esperaria que o comportamento acima você descreve. Mas eu não acho que o padrão já exige ou mandatos isso.

Agora, o padrão é claro que a ordem parcial é agnóstico com o tipo que é usado na chamar a função (eu acredito é isso que você está se referindo quando você descrevê-lo como uma propriedade estática e sendo contexto independente).

A norma também é claro que só se preocupa com ordenação parciais (invoca ordenação parcial) entre modelos de função durante o processo de resolução de sobrecarga (13.3.3 / 1) se e só se não poderiam escolher a função melhor com base no ICS do ou se um é um modelo e o outro não é. [Ordenação parcial do molde classe especializações parciais é um problema separado e na minha mente usa o contexto relevante (outras definições de modelo) que exige a instanciação dessa classe particular.]

Assim, na minha opinião, uma vez que as máquinas de ordenação parcial de modelos de função é invocada quando a sobrecarga resolução é realizada, tem que usar uma parte relevante do contexto (definições e especializações modelo) disponíveis no momento em que a resolução de sobrecarga está sendo feito.

Assim, com base na minha interepretation, de acordo com o seu exemplo usando 'template struct A' acima, o código é válido. A ordenação parcial não é feito no contexto definição. Mas, se / quando acontecer de você invocar a resolução de sobrecarga entre as duas funções por escrever uma chamada para f ((int *) 0,0) - e naquele tempo quando o compilador quer tentativas para montar uma declaração candidato ou parcialmente fim-los (se ele fica para o passo-ordenação parcial) Se um inválido resultados de expressão ou do tipo como parte do tipo de função, SFINAE ajuda-nos para fora e diz -nos esse modelo dedução falha (tanto quanto ordenação parcial está em causa, o que implica que um não pode ser mais especializado do que o outro se nós não poderíamos até mesmo transformar o modelo).

Agora, no que se refere POIs - se você estiver convencido de que, como eu, que os tipos de função transformados devem representam instantiations implícitas usando listas de argumentos modelo explicitamente fornecido (usando os tipos gerados exclusivamente) em seguida, as seguintes citações padrão são relevantes:

14.6.4.1/1 Para uma especialização de modelo função, uma especialização de modelo função membro, ou uma especialização para um função de membro ou membro de dados estáticos de um modelo de classe, se a especialização é implicitamente instanciado porque ele é referenciado a partir de dentro de um outro modelo de especialização e o contexto a partir da qual ele é referenciado depende de um parâmetro de modelo, o ponto de instanciação da especialização é o ponto de instanciação da especialização de fechamento.

A forma como eu interpretar isso é que o POI do tipo de função transformada e o tipo de função origianl é o mesmo que o POI para essas funções criados pela chamada de função real.

litb: Desde ordenação parcial é bastante única a property of the syntactic form of parameters (i.e "T*" against "T(*)[N]"), Eu votaria para que altera a especificação (como "se Q aparece em um especificador nome aninhada de a-id qualificado nomear um tipo, em seguida, o tipo de chamada é "Q") Ou dizendo que o tipo de chamada é outro original de tipo. This means that in template<typename T> void f(T, typename Const<T>::type*); the argument list is (Q, R*), for example. Same for template<typename T> void f(T*, typename ConstI<sizeof(T)>::type); the arg lisst would be (Q*, R). A similar rule would be needed for non-type parameters, of course. Eu teria que pensar sobre isso e fazer alguns casos de teste para ver se isso renderia ordenações naturais, no entanto.

Aah - agora você está sugerindo uma possível solução que resolve a ambiguidade em favor do que nós todos intuitivamente esperar - este é um problema separado, e enquanto eu como a direção que você está tomando, como você, eu também teria que colocar algum pensamento nele antes de proclamar a sua exequibilidade.

Obrigado por continuar a discussão. Desejo SO não apenas limitá-lo a colocar comentários.

Uma vez que você pode editar os meus posts, sinta-se livre para responder dentro do posto se for mais fácil.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top