Dedução incompatível de tipos automáticos entre diferentes compiladores c ++

10

Então, estou tentando implementar o produto escalar ( https://en.wikipedia.org/wiki/Dot_product ) em algum sabor do C ++ moderno e criei o seguinte código:

#include <iostream>

template<class... Args>
auto dot(Args... args)
{
    auto a = [args...](Args...)
    { 
        return [=](auto... brgs)
        {
            static_assert(sizeof...(args) == sizeof...(brgs));

            auto v1 = {args...}, i1 = v1.begin();
            auto v2 = {brgs...}, i2 = v2.begin();
            typename std::common_type<Args...>::type s = 0;

            while( i1 != v1.end() && i2!= v2.end())
            {
                s += *i1++ * *i2++;
            } 
            return s;
        };
    };
  return a(std::forward<Args>(args)...);
}

int main()
{
    auto a = dot(1,3,-5)(4,-2,-1);
    std::cout << a << std::endl;
}

Online: https://gcc.godbolt.org/z/kDSney e também: cppinsights

O código acima compila e executa bem com g++, no entanto clang( icce msvc) sufoca:

clang++ ./funcpp.cpp --std=c++17                                                                                                                                                                                                                                                        
./funcpp.cpp:12:4: error: 'auto' deduced as 'std::initializer_list<int>' in declaration of 
        'v1' and deduced as 'const int *' in declaration of 'i1'
                        auto v1 = {args...}, i1 = v1.begin();
                        ^         ~~~~~~~~~       ~~~~~~~~~~
./funcpp.cpp:28:11: note: in instantiation of function template specialization 
        'dot<int, int, int>' requested here
        auto a = dot(1,3,-5)(4,-2,-1);
                 ^
1 error generated.

Agora, se eu quebrar a definição de v1, v2, i1, i2como:

auto v1 = {args...} ;
auto i1 = v1.begin();
auto v2 = {brgs...};
auto i2 = v2.begin();

clange msvcnão tem problemas, iccainda engasga:

<source>(10): error: static assertion failed

                static_assert(sizeof...(args) == sizeof...(brgs));

                ^

          detected during instantiation of "auto dot(Args...) [with Args=<int, int, int>]" at line 30

compilation aborted for <source> (code 2)

Execution build compiler returned: 2

No entanto, se eu remover a ofensa static_assert, icctambém não haverá problemas para compilar o código.

E além da pergunta (típica): o que é certo e por que :) a pergunta concreta é:

De acordo com [dcl.spec.auto]:

se o tipo que substitui o tipo de espaço reservado não for o mesmo em cada dedução, o programa não está formado

clangidentificou corretamente que existem dois tipos diferentes definidos na linha em questão: 'auto' deduced as 'std::initializer_list<int>' in declaration of 'v1' and deduced as 'const int *' in declaration of 'i1'por isso, gostaria de ouvir suas opiniões sobre:

Obrigado por ler esta longa pergunta. (Como um bônus se alguém pudesse responder por que iccfalha no static_assertseria ótimo.)

Ferenc Deak
fonte
11
Para que serve std::forward<Args>(args)aqui?
Evg
test.cpp: Na função 'int main ()': test.cpp: 4: 5: error: dedução inconsistente para 'auto': 'long int' e depois 'double' 4 | auto i = 0l, f = 0,0; | ^ ~~~ Com g ++, parece que não estende isso em geral.
N314159 14/11/19
imprimir os tipos nos fornece: std :: initializer_list <int>, int const * std :: initializer_list <int>, int const * em g ++, portanto deduz tipos diferentes.
N314159 14/11/19
3
O GCC não compila auto v = { 1, 2, 3 }, i = v.begin(); . Não entenda que ele compila o mesmo lambda insiede. Exemplo mínimo: gcc.godbolt.org/z/a5XyxU . Ele até compila dentro de um functor definido pelo usuário: gcc.godbolt.org/z/eYutyK , ou uma função de modelo: gcc.godbolt.org/z/jnEYXh .
Daniel Langr
2
@underscore_d Suponho que sim. O exemplo muito mínimo é template <typename T> void f(T a) { auto v = {a}, i = v.begin(); }, quando invocado, por exemplo, como f(1);. Reescrito como void f(int a) { /* same body */ }causa erro de compilação.
Daniel Langr

Respostas:

2

Expandindo meus comentários:

O g ++ nem sempre faz isso, considere o exemplo auto i = 0l, f = 0.0;, ele fornece o erro:

test.cpp: In function int main()’:
test.cpp:4:5: error: inconsistent deduction for auto’: long int and then double
    4 |     auto i = 0l, f = 0.0;

Se compilarmos seu programa e imprimirmos os tipos de variáveis ​​( com este método ), obteremos a seguinte saída:

v1: std::initializer_list<int>, i1: int const*
v2: std::initializer_list<int>, i2: int const*

usando o gcc versão 9.2.0, com sinalizadores -std=c++17 -pedantic -Wall -Wextrasem nenhum aviso ou erro.

Pelo seu comentário sobre o padrão, este programa está mal formado e o padrão especifica que deve ser emitida uma mensagem de diagnóstico (aviso ou erro), a menos que seja especificado de outra forma (o que não é, neste caso). Portanto, eu diria que isso é um bug no gcc.

É um bug conhecido .

n314159
fonte
Como é um bug muito conveniente ... alguns podem argumentar que é um recurso: D Obrigado por suas idéias!
Ferenc Deak
Seria ótimo se alguém pudesse registrar um bug g++sobre isso.
underscore_d
11
Eu nunca fiz isso antes, mas posso investigar em poucas horas.
N314159 14/11/19
gcc.gnu.org/bugzilla/show_bug.cgi?id=92509 Espero que este seja um relatório sensato de erros.
N314159
0

A static_assertfalha no ICC é definitivamente um bug. Encontrei uma solução simples movendo static_assert- se para uma função separada. Solução não muito elegante, mas funciona.

Com pequenas modificações, este é o código que compila com GCC, Clang e ICC:

template<std::size_t size, class... Args>
void args_no_guard(Args... args)
{
    static_assert(sizeof...(args) == size);
}

template<class... Args>
auto dot(Args... args)
{
    return [=](auto... brgs)
    {
        constexpr auto n = sizeof...(args);
        args_no_guard<n>(brgs...);

        using T = std::common_type_t<decltype(args)..., decltype(brgs)...>;
        const T v1[]{static_cast<T>(args)...};
        const T v2[]{static_cast<T>(brgs)...};

        T dot = 0;
        for (std::size_t i = 0; i < n; ++i)
            dot += v1[i] * v2[i];
        return dot;
    };
}
Evg
fonte
Existe um bug contra o ICC para isso? :-)
underscore_d
Você disse que há claramente um bug no ICC, então me pergunto se eles já tiveram um relatório desse bug enviado por alguém. Caso contrário, pode ser um bom momento para criar um.
Sublinhado_d
11
@underscore_d, ainda não verifiquei, mas irei.
Evg