Несоответствующий вывод авто типов между различными компиляторами c ++

10

Итак, я пытаюсь реализовать точечный продукт ( https://en.wikipedia.org/wiki/Dot_product ) в какой-то разновидности современного C ++ и придумал следующий код:

#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;
}

Онлайн: https://gcc.godbolt.org/z/kDSney, а также: cppinsights

Приведенный выше код компилируется и выполняется хорошо g++, однако clangiccи msvc) захлебнется:

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.

Теперь, если я разбить определение v1, v2, i1, i2как:

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

clangи msvcпроблем нет, iccвсе равно задыхается

<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

Однако, если я уберу оскорбление, static_assertто iccне будет проблем с компиляцией кода.

И кроме (типичного) вопроса: что правильно и почему :) конкретный вопрос:

По словам [dcl.spec.auto]:

если тип, который заменяет тип заполнителя, не является одинаковым при каждом вычете, программа плохо сформирована

clangправильно определили, что в рассматриваемой строке есть два разных типа: 'auto' deduced as 'std::initializer_list<int>' in declaration of 'v1' and deduced as 'const int *' in declaration of 'i1'поэтому я хотел бы услышать ваше мнение:

  • я столкнулся с недокументированным расширением g ++, учитывая эту конкретную ситуацию (не упомянутую в https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/C_002b_002b-Extensions.html#C_002b_002b-Extensions ), поскольку g ++, насколько мне известно правильно обрабатывает различные типы в списке автоматического объявления,
  • или по какой-то причине g ++ не сделал вывод, что эти два типа различны (... хм ...)
  • или что-то другое?

Спасибо за чтение этого длинного вопроса. (В качестве бонуса, если кто-то может ответить, почему iccне удается, static_assertбыло бы здорово.)

Ференц Дик
источник
1
Какая тут польза std::forward<Args>(args)?
Evg
test.cpp: В функции 'int main ()': test.cpp: 4: 5: ошибка: непоследовательное удержание для 'auto': 'long int' и затем 'double' 4 | auto i = 0l, f = 0.0; | ^ ~~~ С g ++, похоже, это вообще не расширяется.
n314159
печать типов дает нам: std :: initializer_list <int>, int const * std :: initializer_list <int>, int const * в g ++, поэтому он выводит разные типы.
n314159
3
GCC не компилируется auto v = { 1, 2, 3 }, i = v.begin(); . Не понимаю, что он компилирует ту же самую лямбду. Минимальный пример: gcc.godbolt.org/z/a5XyxU . Он даже компилируется внутри пользовательского функтора: gcc.godbolt.org/z/eYutyK или функции шаблона: gcc.godbolt.org/z/jnEYXh .
Даниэль Лангр
2
@underscore_d Я так полагаю. Самый минимальный пример - template <typename T> void f(T a) { auto v = {a}, i = v.begin(); }когда вызывается, например, как f(1);. Переписано как void f(int a) { /* same body */ }вызывает ошибку компиляции.
Даниэль Лангр

Ответы:

2

Исходя из моих комментариев:

g ++ делает это не всегда, рассмотрим пример auto i = 0l, f = 0.0;, он выдает ошибку:

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;

Если мы скомпилируем вашу программу и напечатаем типы переменных ( с помощью этого метода ), мы получим следующий вывод:

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

используя gcc версии 9.2.0, с флагами -std=c++17 -pedantic -Wall -Wextraбез предупреждения или ошибки.

По вашим комментариям к стандарту эта программа некорректна и стандартна указывается, что должно быть выдано диагностическое сообщение (предупреждение или ошибка), если не указано иное (в данном случае это не так). Следовательно, я бы сказал, что это ошибка в gcc.

Это известная ошибка .

n314159
источник
Так как это очень удобная ошибка ... некоторые могут поспорить, что это особенность: D Спасибо за ваши идеи!
Ференц Дик
Было бы здорово, если бы кто-то мог подать ошибку g++об этом.
underscore_d
1
Я никогда не делал этого раньше, но я могу разобраться в этом за несколько часов.
n314159
gcc.gnu.org/bugzilla/show_bug.cgi?id=92509 Надеюсь, что это разумное сообщение об ошибке.
n314159
0

static_assertНеудача на ICC, безусловно, ошибка. Я нашел простой обходstatic_assert в отдельную функцию. Не очень элегантное решение, но оно работает.

С небольшими изменениями этот код компилируется с GCC, Clang и 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
источник
Есть ли ошибка для ICC для этого? :-)
underscore_d
Вы сказали, что в ICC явно есть ошибка, поэтому мне интересно, есть ли у них отчет об этой ошибке, представленный кем-то. Если нет, то сейчас самое время его создать.
underscore_d
1
@underscore_d, я еще не проверял, но я проверю.
Evg