简体   繁体   中英

C++17 almost uniform initialization

At the end of this video (starting at 15:57) there is advice on how to use almost uniform initialization in C++17: video here

The gist goes like this: use always direct initialization auto a{...}; and MyType a{...}; Do not use copy initialization = {...} for your types.

#include <iostream>


struct MyType {
    explicit MyType(std::initializer_list<int>) {
        std::cout << "Called std::initializer_list<int>" << std::endl;
    }

    explicit MyType(int) {
        std::cout << "Called int." << std::endl;
    }

    MyType(int, int, int) {
        std::cout << "Called int, int, int" << std::endl;
    }
};



int main() {
    MyType calls_init_list{10}; //Calls initializer_list<int>
    MyType calls_init_list_2{10, 20}; //Calls initializer_list<int>
    MyType calls_init_list_3{10, 20, 30}; //Calls initializer_list<int>

    MyType compile_error = {10, 20, 30}; //Compile error
}

If I remove explicit from the first constructor it will call the 4th call also with initializer_list<int>

  1. What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?
  2. Is it even possible to call the other constructors in the presence of the initializer list constructor?
  3. any design recommendations to avoid abandoning the general rule adviced in the video? It would be nice to finally have something that makes sense, C++ initialization is the most terrible part of it probably.

In your case, to call MyType(int, int, int) or explicit MyType(int) , you have to use () syntax instead of {} .

Basically, I don't think it's a good idea to always use {} syntax. For example, as of C++17, all emplace methods in the standard library are using () internally instead of {} . For example, the code

std::vector<std::vector<int>> vv;
vv.emplace_back(2, 1);

emplaces <1, 1> not <2, 1> . That's also why standard containers do not support emplace construction of aggregate types.

In my opinion, genuine uniform initialization that you can stick to is one that performs () initialization if possible, and falls back to {} otherwise (eg, for aggregate types). Also see this . Possible implementation:

template <typename...>
struct paren_initable: std::false_type {};

template <typename T, typename... Us>
struct paren_initable<decltype((void)T(std::declval<Us>()...)), T, Us...>
 : std::true_type {};

template <typename T, typename... Us>
inline constexpr bool paren_initable_v = paren_initable<void, T, Us...>::value;

template <typename T, typename... Us>
T emplace(Us&&... us) {
  if constexpr (paren_initable_v<T, Us...>) {
    return T(std::forward<Us>(us)...);
  }
  else {
    return T{std::forward<Us>(us)...};
  }
}

What changes should I need for being able to call (int) and (int, int, int) following the rule in the video?

Remove the initializer_list<int> constructor. That is the only way to make it work.

Is it even possible to call the other constructors in the presence of the initializer list constructor?

Yes, so long as the types in the braced-init-list cannot match those in the any initializer_list<T> constructors. They always have primacy.

Hence why it's derisively called " almost uniform initialization".

The typical solution is to add some tag type to the non- initializer_list constructors:

struct tag_t {};
constexpr inline tag_t tag;

struct MyType {
    explicit MyType(std::initializer_list<int>) {
        std::cout << "Called std::initializer_list<int>" << std::endl;
    }

    MyType(tag_t, int) {
        std::cout << "Called int." << std::endl;
    }

    MyType(tag_t, int, int, int) {
        std::cout << "Called int, int, int" << std::endl;
    }
};

int main() {
    MyType three_int = {tag, 10, 20, 30}; //Calls 3-`int` constructor
}

any design recommendations to avoid abandoning the general rule adviced in the video?

Well, considering that the "general rule" is not a good rule (his slide contains the quintessential counter-example: try to call the size+value version of vector<int> with braces), it's better to abandon it. Minor quibbles about what auto a{2}; translates into are irrelevant next to being literally incapable of calling some constructors.

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