Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does this compile with Visual Studio 2013 but not g++-4.8.1?

The following example (ideone) compiles and works when using Visual Studio 2013 on Windows 7, but not with g++4.8.1 on Ubuntu 13.10.

#include <cassert>
#include <cstdlib>

#include <array>
#include <iostream>
#include <numeric>
#include <utility>

// Wraps a std::array of TKey/TValue pairs and provides a method
// to randomly select a TKey with TValue bias.
template< typename TKey, typename TValue, std::size_t TSize >
class weights final
{
    public:
        using pair = const std::pair< const TKey, const TValue >;
        using array = const std::array< pair, TSize >;

        weights( array values )
            : values_{ values }
            , sum_{ std::accumulate(values_.begin(), values_.end(), 0, [](TValue total, const pair& p){ return total + p.second; }) }
        {}

        // Implements this algorithm
        // http://stackoverflow.com/a/1761646/331024
        const TKey get() const
        {
            // The real code uses c++11 <random> features,
            // which I've removed for brevity.
            auto weight_rand = static_cast< TValue >( std::rand() % sum_ );

            for ( std::size_t i = 0; i < TSize; ++i )
            {
                if (weight_rand < values_[i].second)
                {
                    return values_[i].first;
                }
                weight_rand -= values_[i].second;
            }
            assert(false);
        }

    private:
        array values_;
        const TValue sum_;
};

enum class direction
{
    NORTH,
    SOUTH,
    EAST,
    WEST
};

// For convenience create a type to map the above
// four-value enumeration to integer weights.
using w4i = weights< direction, int, 4 >;

// Map the directions with a weight.
static const w4i direction_weights = w4i::array{
    {
        w4i::pair{ direction::NORTH, 2 },
        w4i::pair{ direction::EAST, 1 },
        w4i::pair{ direction::SOUTH, 3 },
        w4i::pair{ direction::WEST, 1 }
    }
};

int main()
{
    std::cout << (int)direction_weights.get() << std::endl;    

    return 0;
}

Visual Studio 2013 can compile and run the code. g++-4.8.1 fails to compile, outputting the following error:

$ g++ -std=c++11 -Wall -Wextra -pedantic weights.cpp -o weights
weights.cpp: In instantiation of ‘weights<TKey, TValue, TSize>::weights(array) [with TKey = direction; TValue = int; long unsigned int TSize = 4ul; weights<TKey, TValue, TSize>::array = const std::array<const std::pair<const direction, const int>, 4ul>]’:
weights.cpp:67:5:   required from here
weights.cpp:20:131: error: could not convert ‘values’ from ‘weights<direction, int, 4ul>::array {aka const std::array<const std::pair<const direction, const int>, 4ul>}’ to ‘const std::pair<const direction, const int>’, sum_{ std::accumulate(values_.begin(), values_.end(), 0, [](TValue total, const pair& p){ return total + p.second; }) }

How do I fix/modify it to work with both compilers?

like image 677
x-x Avatar asked Mar 19 '14 03:03

x-x


2 Answers

Your problem is trying to use universal initialization, which introduces ambiguity. Taking your code and creating the minimal example results in:

Doesn't work:

struct weights
{
    weights(std::array<int, 1> values)
        : values_{values}
    {}

    std::array<int, 1> values_;
};

Works:

struct weights
{
    weights(std::array<int, 1> values)
        : values_(values)
    {}

    std::array<int, 1> values_;
};

The problem is that it becomes ambiguous as to what {values} should do. Should it create an initializer list with one element (values) in it? Or should it act as a universal initializer/brace-init and have identical behavior to parentheses? It looks like GCC and clang are doing the first (which results in a type mismatch), while Visual Studio is doing the second (which correctly type checks).

If you want it to work on both, use parentheses.

I don't know if this is a bug in GCC/clang or Visual Studio, or an ambiguity in the standard.

Edit:

For a simpler example, consider:

std::array<int, 2> a = {1, 2};
std::array<int, 2> b{a}; // error: no viable conversion from 'std::array<int, 2>' to 'value_type' (aka 'int')
std::array<int, 2> b(a); // works

The first is creating an initializer list with a single std::array<int, 2> object inside it, while the second one is correctly calling the copy constructor.

like image 156
Cornstalks Avatar answered Oct 17 '22 03:10

Cornstalks


This is not a bug in std::array

struct array {
  int i;
};

int main()
{
  array a;
  array b{a};
}

G++ and Clang both reject this, as they are required to by the standard, see DR 1467 which was opened after I reported GCC PR 51747

like image 22
Jonathan Wakely Avatar answered Oct 17 '22 03:10

Jonathan Wakely