3

I have a class called Shape, which can be initialized from any iterable, and a class called Array, which simply contains a Shape. However, I'm getting a compile error I can't explain when I try to initialize an Array:

class Shape
{
public:
    template<typename Iterator>
    Shape(Iterator first, Iterator last)
        : m_shape(first, last) {}

    template <typename Iterable>
    Shape(const Iterable& shape)
        : Shape(shape.begin(), shape.end()) {}

    template<typename T>
    Shape(std::initializer_list<T> shape)
        : Shape(shape.begin(), shape.end()) {}

private:
    std::vector<std::size_t> m_shape;
};

class Array
{
public:
    Array(const Shape& shape)
        : m_shape(shape) {}
private:
    Shape m_shape;
};

int main() {
    Shape s{0};       // ok
    Array a1({1, 2}); // ok
    Array a2({0});    // error
}

The compilation error appears on the second constructor of Shape:

prog.cxx:35:16:   required from here
prog.cxx:14:23: error: request for member ‘begin’ in ‘shape’, which is of non-class type ‘const int’
         : Shape(shape.begin(), shape.end()) {}
                 ~~~~~~^~~~~
prog.cxx:14:38: error: request for member ‘end’ in ‘shape’, which is of non-class type ‘const int’
         : Shape(shape.begin(), shape.end()) {}
                                ~~~~~~^~~

I don't understand what is happening here. Why is the Iterable constructor called instead of the initializer_list<T> constructor? What's the difference between the Shape constructor with {0} and the Array constructor?

3
  • I can't reproduce; your code compile fine (no error in s5 line) with my g++ 6.3.0 and with my clang++ 3.8.1 (I mean... if you correct NDShape, for the second constructor, in Shape, obviously). Which compiler are you using? Commented Oct 12, 2017 at 9:32
  • You are right, sorry. I simplified the code too much. The updated code should give you an error now. Thanks! Commented Oct 12, 2017 at 11:22
  • now I have an error but it's completely different from the one you reported; can you confirm the "no matching function for call to ‘cbegin(const int&) [...]" error? Commented Oct 12, 2017 at 12:13

1 Answer 1

3

The code is ill-formed, but not for the reason gcc claims it is. When you write:

Array a2({0});

We do overload resolution over all the constructors of Array using the initializer {0}.

Option #1 is:

Array(Shape const& );

on which we would recurse into attempting to copy-initialize Shape with {0} which ends up invoking the std::initializer_list<int> constructor template due to preferential treatment of std::initializer_list during list-initialization.

However, that's just one option. Option #2 is:

Array(Array&& );

The implicit move constructor. To check if that's a candidate, we see if we can initialize Array with {0}, which basically starts over again. In this next layer, we see if we can initialize Shape with 0 (since we're one layer removed), and we can - that's your accept-all-the-things constructor template. This does involve two user-defined conversion sequences, but that's ok for list-initialization.

So we have two options:

  • Option #1: {0} --> Shape
  • Option #2: 0 --> Shape --> Array

Neither is better than the other, so the call is ambiguous.


The easy fix is to add a constraint to your constructor template such that it actually is a range. This is generally good practice anyway, since you don't want is_constructible_v<Shape, int> to be true...

Sign up to request clarification or add additional context in comments.

2 Comments

+1. Interestingly, one could remove option #2 by suppressing the conversion Shape --> Array by writing explicit Array(const Shape&). Clang-5.0.0 (happy) and GCC 7.2 (complaining) do not agree if that fixes the issue. Does it make sense to submit a bug report? Suppressing the conversion int --> Shape by declaring the corresponding constructor as explicit works as expected (and is simpler than adding constraints). You may want to add that single-argument constructors should always be considered as explicit candidates.
@Julius Yeah, I think that's a gcc bug. But explicit isn't really sufficient, since it's not like Shape s(0); is valid either - you do need the constraints anyway.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.