Deducción no coincidente de tipos automáticos entre diferentes compiladores de c ++

10

Entonces, estoy tratando de implementar el producto punto ( https://en.wikipedia.org/wiki/Dot_product ) en un poco de C ++ moderno y se me ocurrió el siguiente 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;
}

En línea: https://gcc.godbolt.org/z/kDSney y también: cppinsights

El código anterior se compila y se ejecuta muy bien con g++, sin embargo clang( iccy msvc) estrangularlo:

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.

Ahora bien, si rompo la definición de v1, v2, i1, i2como:

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

clangy msvcno tengo problemas, iccaún se ahoga:

<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

Sin embargo, si elimino al infractor static_assert, icctampoco tendrá problemas para compilar el código.

Y al lado de la pregunta (típica): cuál es la razón y por qué :) la pregunta concreta es:

De acuerdo a [dcl.spec.auto]:

Si el tipo que reemplaza el tipo de marcador de posición no es el mismo en cada deducción, el programa está mal formado

clangidentificó correctamente que hay dos tipos diferentes definidos en la línea en cuestión: 'auto' deduced as 'std::initializer_list<int>' in declaration of 'v1' and deduced as 'const int *' in declaration of 'i1'así que me gustaría escuchar sus opiniones si:

Gracias por leer esta larga pregunta. (Como beneficio adicional, si alguien pudiera responder por qué iccfalla static_assert, sería genial).

Ferenc Deak
fuente
1
¿De qué sirve std::forward<Args>(args)aquí?
Evg
test.cpp: en la función 'int main ()': test.cpp: 4: 5: error: deducción inconsistente para 'auto': 'long int' y luego 'double' 4 | auto i = 0l, f = 0.0; El | ^ ~~~ Con g ++, por lo que parece que no extiende esto en general.
n314159
imprimir los tipos nos da: std :: initializer_list <int>, int const * std :: initializer_list <int>, int const * en g ++, por lo que deduce diferentes tipos.
n314159
3
GCC no compila auto v = { 1, 2, 3 }, i = v.begin(); . No entiendo que compila el mismo insiede lambda. Ejemplo mínimo: gcc.godbolt.org/z/a5XyxU . Incluso se compila dentro de un functor definido por el usuario: gcc.godbolt.org/z/eYutyK , o una función de plantilla: gcc.godbolt.org/z/jnEYXh .
Daniel Langr
2
@underscore_d supongo que sí. El ejemplo mínimo es template <typename T> void f(T a) { auto v = {a}, i = v.begin(); }, cuando se invoca, por ejemplo, como f(1);. Reescrito como void f(int a) { /* same body */ }causa error de compilación.
Daniel Langr

Respuestas:

2

Ampliando de mis comentarios:

g ++ no siempre hace esto, considere el ejemplo auto i = 0l, f = 0.0;, da el error:

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;

Si compilamos su programa e imprimimos los tipos de variables ( con este método ), obtenemos el siguiente resultado:

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

usando gcc versión 9.2.0, con banderas -std=c++17 -pedantic -Wall -Wextrasin ninguna advertencia o error.

Por su comentario sobre el estándar, este programa está mal formado y el estándar especifica que debe emitirse un mensaje de diagnóstico (advertencia o error) a menos que se especifique lo contrario (que no es así, en este caso). Por lo tanto, diría que este es un error en gcc.

Es un error conocido .

n314159
fuente
Dado que es un error muy conveniente ... algunos podrían argumentar que es una característica: D ¡Gracias por sus ideas!
Ferenc Deak
Sería genial si alguien pudiera presentar un error en contra g++de esto.
underscore_d
1
Nunca lo había hecho antes, pero puedo investigarlo en unas pocas horas.
n314159
gcc.gnu.org/bugzilla/show_bug.cgi?id=92509 Espero que sea un informe de error sensible.
n314159
0

La static_assertfalla en ICC es definitivamente un error. Encontré una solución simple al pasar static_asserta una función separada. Solución no muy elegante, pero funciona.

Con ligeras modificaciones, este es el código que se compila con 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
fuente
¿Hay algún error contra ICC por eso? :-)
underscore_d
Dijiste que claramente hay un error en ICC, así que me pregunto si ya tenían un informe de este error enviado por alguien. Si no, este podría ser un buen momento para crear uno.
underscore_d
1
@underscore_d, aún no lo he comprobado, pero lo haré.
Evg