Mismatched deduction of auto types between different c++ compilers
Asked Answered
D

2

10

So, I am trying to implement the dot product (https://en.wikipedia.org/wiki/Dot_product) in some flavour of modern C++ and came up with the following code:

#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 and also: cppinsights

The code above compiles and executes nicely with g++, however clang (and icc and msvc) choke on it:

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.

Now, if I break up the definition of v1, v2, i1, i2 like:

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

clang and msvc have no problems, icc still chokes:

<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

However if I remove the offending static_assert then icc has no issues compiling the code either.

And beside of the (typical) question: which is right and why :) the concrete question is:

According to [dcl.spec.auto] :

if the type that replaces the placeholder type is not the same in each deduction, the program is ill-formed

clang correctly identified that there are two different types defined in the line in question: 'auto' deduced as 'std::initializer_list<int>' in declaration of 'v1' and deduced as 'const int *' in declaration of 'i1' so I'd like to hear your opinions whether:

Thanks for reading through this long question. (As a bonus if someone could answer why icc fails on the static_assert would be great.)

Dru answered 14/11, 2019 at 11:21 Comment(7)
What's the use of std::forward<Args>(args) here?Amary
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; | ^~~~ With g++, so it seems it does not extend this in general.Feucht
printing the types gives us: std::initializer_list<int>, int const* std::initializer_list<int>, int const* in g++, so it deduces different types.Feucht
@Amary none, forgot to remove it :)Dru
GCC does not compile auto v = { 1, 2, 3 }, i = v.begin();. Don't understand that it compiles the same insiede lambda. Minimal example: gcc.godbolt.org/z/a5XyxU. It even compiles inside a user-defined functor: gcc.godbolt.org/z/eYutyK, or a template function: gcc.godbolt.org/z/jnEYXh.Compliancy
@DanielsaysreinstateMonica I guess it's template-ness causing problems somehow?Divert
@Divert I suppose so. The very minimal example is template <typename T> void f(T a) { auto v = {a}, i = v.begin(); }, when invoked, e.g., as f(1);. Rewritten as void f(int a) { /* same body */ } causes compilation error.Compliancy
F
2

Expanding from my comments:

g++ does not do this always, consider the example auto i = 0l, f = 0.0;, it gives the 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;

If we compile your program and print the types of the variables (with this method), we get the following output:

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

using gcc version 9.2.0, with flags -std=c++17 -pedantic -Wall -Wextra without any warning or error.

By your comment of the standard this program is ill-formed and the standard specifies that there should be emitted a diagnostic message (warning or error) unless otherwise specified (which it is not, in this case). Hence I would say that this is a bug in gcc.

It is a known bug.

Feucht answered 14/11, 2019 at 12:23 Comment(4)
Since it's a very convenient bug ... some might argue it to be a feature :D Thanks for your insights!Dru
It would be great if someone could file a bug against g++ about this.Divert
I have never done that before but I can look into it in a few hours.Feucht
gcc.gnu.org/bugzilla/show_bug.cgi?id=92509 Hope that is a sensible bug report.Feucht
A
0

The static_assert fail on ICC is definitely a bug. I found a simple workaround by moving static_assert into a separate function. Not very elegant solution, but it works.

With slight modifications, this is the code that compiles with GCC, Clang and 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;
    };
}
Amary answered 14/11, 2019 at 13:4 Comment(3)
Is there a bug against ICC for that? :-)Divert
You said there's clearly a bug in ICC, so I wonder if they already had a report of this bug submitted by someone. If not, this might be a good time to create one.Divert
@underscore_d, I haven't checked yet, but I will.Amary

© 2022 - 2024 — McMap. All rights reserved.