4

I have an issue in that I cannot use a certain constructor with an array initializer when using VS2017 (C++14, C++17 and ISO latest).

I get an C2397 conversion from 'double' to 'unsigned int' requires a narrowing conversion error when it should be invoking the constructor with a container populated with a single element.

#include <vector>

class obj
{
public:
    obj(const std::vector<double>& values, unsigned int stride)
        : values_(values), stride_(stride)
    {
    }

    obj(unsigned int m, unsigned int n)
        : stride_(n)
    {
    }

private:
    unsigned int stride_;
    std::vector<double> values_;
};

int main(int argc, char** argv)
{
    obj m(1, 1);                // correct constructor called.
    obj mm({ 42.0 }, 1);        // Error C2397

    return 0;
}

I can fix this by explicitly declaraing the container...

    obj mm(std::vector<double>({ 42.0 }), 1);

Or initializing the container with more than one item...

    obj mm({ 42.0, 12.0 }, 1);

The latter obviously being of no use, and the former being slightly annoying as it's a corner case for containers with a single item (albeit not the end of the world). I thought this might be problematic for doubles only (having no literal declaration), however it even occurs for floats when initializing them with literals too. i.e container is std::vector<float>, the following line still errors with C2397.

    obj mm({ 42.0f }, 1);

I don't tend to believe in compiler bugs myself having not come across many (although they obviously exist), however I can't help but think this may be one, or if not, is there any mention in the standard how to deal with this situation. Ideally I would like to be able to use the array initializer without explicitly declaring the container type as I can when more than one item exists in the container. Is this possible?

2
  • g++ 9.2 says: error: narrowing conversion of '4.2e+1' from 'double' to 'unsigned int' [-Wnarrowing] and error: narrowing conversion of '4.2e+1f' from 'float' to 'unsigned int' [-Wnarrowing] in the float case. Commented Feb 4, 2020 at 10:32
  • @Scheff Thanks yes this is a fix. Commented Feb 4, 2020 at 10:44

3 Answers 3

5

Using {{ and }} is the fix in all cases

obj mm({{ 42.0 }}, 1); 

and

obj mm({{ 42.0, 12.0 }}, 1);

Although of course there is no ambiguity in the second case (the use of single braces is exploiting brace-elision).

This question affords a good introduction to the topic: Brace elision in std::array initialization

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

2 Comments

Thanks yes this appears to be the answer. I guess it's either attempt to enforce this syntax, or provide the initializer_list constructor for the single case of a single item populated container.
Brace elision is new terminology and behavior for me. Thanks I will digest the topic you posted :)
1

Do you mean the following

obj mm({ 1, 42.0 }, 1);

or the following

obj mm({ { 42.0 } }, 1);

1 Comment

Thanks for your answer, I mean the latter.
1

A constructor with std::initializer_list could be added to the object.

Sample:

#include <iostream>
#include <vector>

struct Obj {
  std::vector<double> values;
  unsigned stride;

  Obj(std::initializer_list<double> values, unsigned stride = 1):
    values(values), stride(stride)
  {
    std::cout << "Obj::Obj(std::initializer_list<double>, unsigned)\n";
   }

  Obj(const std::vector<double> &values, unsigned stride = 1):
    values(values), stride(stride)
  {
    std::cout << "Obj::Obj(const std::vector<double>&, unsigned)\n";
  }

  Obj(unsigned m, unsigned stride = 1):
    stride(stride)
  {
    std::cout << "Obj::Obj(unsigned, unsigned)\n";
  }
};

int main()
{
  Obj mm({ 42.0f }, 1);
  Obj mm1(1, 1);
  Obj mm2(std::vector<double>({ 42.0 }), 1);
  Obj mm3({ 42.0, 12.0 }, 1);
  Obj mm4(std::vector<double>{ 42.0 }, 1);
}

Output:

Obj::Obj(std::initializer_list<double>, unsigned)
Obj::Obj(unsigned, unsigned)
Obj::Obj(const std::vector<double>&, unsigned)
Obj::Obj(std::initializer_list<double>, unsigned)
Obj::Obj(const std::vector<double>&, unsigned)

Live Demo on coliru

5 Comments

std::vector<double>{ 42.0 } looks better IMO.
@MarekR I agree. I resembled the samples of OP to demonstrate which constructor is chosen. (I was a bit uncertain whether the choice between initializer list and const std::vector<>& could cause ambiguities I'm not aware of. The initializer stuff is still a bit new for me.) ;-)
Thanks for the answer and thanks for the examples, yes this does fix the issue. I'm unsure what I feel is better to either put a note to say use the double curly brackets syntax, or provide the initializer_list constructor for the single case of a container with a single item. I appreciate your answer though, thanks :)
@lfgtm: I'd plump for the "un-elisioned" syntax if I were you. The single brace syntax is relying on what I think is a tricky corner of C++.
@lfgtm Actually, I resembled partly what std::vector provides for construction. Following the API of the std library is IMHO not the worst thing. ;-) (concerning expectations API users may or may not have)

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.