简体   繁体   中英

cppcheck vs clang-tidy : explict constructor initializer_list

When I run the tools clang-tidy-3.8 and cppcheck-1.72, under the follow code :

#include <initializer_list>
#include <string>
#include <iostream>

using string_list = std::initializer_list<std::string>;

class Foo {
    public:
    explicit Foo(const string_list& strings) {
        for (const auto& ss : strings) {
            std::cout << ss << std::endl;
        }
    }
};

The clang-tidy-3.8 outputs:

$ > clang-tidy -checks='*' main.cpp -- -std=c++11

warning: initializer-list constructor should not be declared explicit [google-explicit-constructor] explicit Foo(const string_list& strings)

However, if I remove the keyword explicit , the cppcheck-1.72 reports:

$ > cppcheck main.cpp --language=c++ --std=c++11 --enable=all

(style) Class 'Foo' has a constructor with 1 argument that is not explicit.

I read at Google Cpp Guide :

Constructors that cannot be called with a single argument should usually omit explicit. Constructors that take a single std::initializer_list parameter should also omit explicit, in order to support copy-initialization (eg MyType m = {1, 2};).

Which tool is correct according the C++ standard?

As @KerrekSB said it depends on what style of construction you want to enforce.

If you make the initializer list constructor explicit then

  • you are disallowing YourType A = {a, b, c}; .
  • but only allowing YourType A({a, b, c}); (or YourType A{{a, b, c}}; ) (I think some compilers accept YourType A{a, b, c} but I find it inconsistent.)

If you don't mark it explict both cases are allowed.

Some people advocate for never using = in constructors (of classes) (not even for initializer list argument), so this is ultimately the style you are enforcing by marking explicit .

Another important side effect of marking explicit that you have to take into account is that you won't be able to pass raw initializer lists as function argument in place of the constructed object (which might be limiting but can be part of further style considerations). Eg fun(arg1, arg2, {a, b, c}) vs. fun(arg1, arg2, YourType({a, b, c})) .

Also note for example that std::vector::vector(std::initializer_list) (or any other std container) is not marked explicit .


My rule of thumb is that I allow = in constructors when the right-hand-side can be represented "faithfully" with the constructed type and at a low computational complexity (eg less than O(N log N) or O(N^2)). IMO there are not many cases where this can be done with an initializer list. The only examples I encountered is for 1) some reincarnation of arrays or list (including std::vector ) 2) unordered linear containers (but IMO excluding ordered containers). 3) Multidimensional arrays (Nested initializer list). 4) Tuples (although there are non-homogeneous initializer list in the language).

(With this rule, I think it is a mistake that it is not explicit for std::set , because std::set will reordered behind the scenes).


What I do in practice is to have a comment with an inline suppression of the cppcheck warning, I feel that a comment is necessary anyway for any implicit single-argument constructor.

    // cppcheck-suppress noExplicitConstructor ; because human-readable explanation here
    YourType(std::initializer_list<value_type> il){...}

and run cppcheck with the option --inline-supp .

(see http://cppcheck.sourceforge.net/manual.pdf#page=19 )

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM