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
( icc
e 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
, i2
como:
auto v1 = {args...} ;
auto i1 = v1.begin();
auto v2 = {brgs...};
auto i2 = v2.begin();
clang
e msvc
não tem problemas, icc
ainda 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
, icc
també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
clang
identificou 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:
- eu atingi uma extensão indevida do g ++, considerando esta situação específica (não mencionada em https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/C_002b_002b-Extensions.html#C_002b_002b-Extensions ) desde o meu conhecimento do g ++ lida corretamente com os diferentes tipos em uma lista de declaração automática,
- ou, por acaso, o g ++ não deduziu que os dois tipos fossem diferentes (... hm ...)
- ou alguma outra coisa?
Obrigado por ler esta longa pergunta. (Como um bônus se alguém pudesse responder por que icc
falha no static_assert
seria ótimo.)
std::forward<Args>(args)
aqui?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 .template <typename T> void f(T a) { auto v = {a}, i = v.begin(); }
, quando invocado, por exemplo, comof(1);
. Reescrito comovoid f(int a) { /* same body */ }
causa erro de compilação.Respostas:
Expandindo meus comentários:
O g ++ nem sempre faz isso, considere o exemplo
auto i = 0l, f = 0.0;
, ele fornece o erro:Se compilarmos seu programa e imprimirmos os tipos de variáveis ( com este método ), obteremos a seguinte saída:
usando o gcc versão 9.2.0, com sinalizadores
-std=c++17 -pedantic -Wall -Wextra
sem 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 .
fonte
g++
sobre isso.A
static_assert
falha no ICC é definitivamente um bug. Encontrei uma solução simples movendostatic_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:
fonte