I recently read cool article: https://akrzemi1.wordpress.com/2015/08/20/can-you-see-the-bug/ Playing with reduced version on ideone I got surprising behaviour:
#include <iostream>
#include <cassert>
using namespace std;
int main() {
const size_t sz=258;
string s{sz,'#'};
assert(2==s.size());
}
does not compile, but same program with const removed compiles:
#include <iostream>
#include <cassert>
using namespace std;
int main() {
size_t sz=258;
string s{sz,'#'};
assert(2==s.size());
}
So my question is this standard required or just compilers decided that one is a compiler warning and other is an compiler error.
If it helps here are the errors and warnings from gcc 5.1 (tnx godbolt)
!!error: narrowing conversion of '258ul' from 'size_t {aka long unsigned int}' to 'char' inside { }
!!warning: narrowing conversion of 'sz' from 'size_t {aka long unsigned int}' to 'char' inside { } [-Wnarrowing]
good guy clang 3.6 gives error in both cases, but the question remains, is one legal and bad C++ and other illegal C++?
std::string
has constructors declared as:
string::string(std::initializer_list<char>);
string::string(std::size_t, char);
When we have list-initialization, the following rule applies:
(N3337 [dcl.init.list]/3):
List-initialization of an object or reference of type T is defined as follows:
- [...]
- Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7). If a narrowing conversion (see below) is required to convert any of the arguments, the program is ill-formed.
The initializer-list constructor is selected due to this rule:
(N3337 [over.match.list]/1):
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
- Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
- [...]
Now since the initializer-list constructor is the best choice, but a narrowing conversion is required to convert the argument, the program is ill formed.
However, I don't think that makes one compiler correct and one incorrect:
(N3337 [intro.compliance]/8):
A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.
The standard has no concept of a warning vs. an error, so GCC is conformant in issuing a warning diagnostic, then going on to compile the program anyway. Note that GCC will issue an error if you pass -pedantic-errors
.
The problem is the std::string
has an initializer lists constructor and it is greedy. {sz,'#'}
is being treaated as an initializer list and you are getting a narrowing conversion warning as it is converting sz
to a char type to make an std::initializer_list<char>
. You can fixe this by calling the constrcutor with ()
instead of {}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With