4
votes

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 (e.g. MyType m = {1, 2};).

Which tool is correct according the C++ standard?

1
What does "correct" mean? How do you want your constructor to be used?Kerrek SB
clang-tidy (3.8) has a test for both that a constructor with only 1 argument has to be explicit and that a constructor with an initializer-list should not be explicit, but cppcheck (1.73) does not have a test for initializer-list. So clang-tidy explicit relaxes the one argument rule for initializer-list.t.niese
Is from the C++11 standards, this relaxed rule?uilianries
@KerrekSB, the "correct" means (for me), which follow the C++ standards. The constructor is just an example, however, I could create a similar object to receive real objects, using the same implementation. In this case I don't know if is just a style rule, by google (clang-tidy), or a C++ rule (cppcheck bug).uilianries
The C++ standard allows any constructor to be declared with or without explicit keyword. Both variants of your code are equally conforming. These tools do not check conformance to the standard (the compiler does that), but to style guides - apparently, different ones. You keep saying that cppcheck enforces what you call "C++ rules" - but note how its message is clearly marked (style)Igor Tandetnik

1 Answers

0
votes

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). E.g. 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 (e.g. 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)