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?

lfgtm
  • 1,037
  • 6
  • 19
  • 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. – Benjamin Bihler Feb 04 '20 at 10:32
  • @Scheff Thanks yes this is a fix. – lfgtm Feb 04 '20 at 10:44

3 Answers3

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

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • 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. – lfgtm Feb 04 '20 at 10:52
  • Brace elision is new terminology and behavior for me. Thanks I will digest the topic you posted :) – lfgtm Feb 04 '20 at 11:00
1

Do you mean the following

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

or the following

obj mm({ { 42.0 } }, 1);
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
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

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • `std::vector{ 42.0 }` looks better IMO. – Marek R Feb 04 '20 at 10:43
  • @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.) ;-) – Scheff's Cat Feb 04 '20 at 10:46
  • 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 Feb 04 '20 at 10:54
  • @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++. – Bathsheba Feb 04 '20 at 10:55
  • 1
    @lfgtm Actually, I resembled partly what [`std::vector`](https://en.cppreference.com/w/cpp/container/vector/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) – Scheff's Cat Feb 04 '20 at 10:57