C++17 almost uniform initialization

南笙酒味 提交于 2020-01-03 05:23:09

问题


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.

回答1:


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.




回答2:


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 (e.g., 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)...};
  }
}


来源:https://stackoverflow.com/questions/49952301/c17-almost-uniform-initialization

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!