The essense of the problem may be shown by the
std::make_from_tuple function.
Case 1: trivial type
struct trivial
{
int x;
double y;
};
make_from_tuple<trivial>(make_tuple(1, 3.14)); // Compilation error.
The example shows that this case is simply not covered by the current version of the standard library. Trivial type can't be created from a tuple by std::make_from_tuple function.
Case 2: the existence of several variants of the constructor
make_from_tuple<vector<size_t>>(make_tuple(5, 1));
// Which vector should be created?
// [1, 1, 1, 1, 1] or [5, 1]?
In the current edition we'll get "five ones". Ant there is no possibility to get "five and one".
Proposed solution
The main idea is tag dispatching.
We create two structs, each of which will signal about the need to call either round or curly brackets (names are rough):
template <typename T>
struct parens_t {};
template <typename T>
constexpr auto parens = parens_t<T>{};
template <typename T>
struct braces_t {};
template <typename T>
constexpr auto braces = braces_t<T>{};
Usage:
auto a = make_unique(parens<A>, 1, 3); // A(1, 3)
auto b = make_unique(braces<A>, 1, 3); // A{1, 3}
auto c = make_from_tuple(parens<A>, make_tuple(100, 3.14)); // A(100, 3.14)
auto d = make_from_tuple(braces<A>, make_tuple(100, 3.14)); // A{100, 3.14}
// Finally we can call the curly brackets. Yay!
Also note that when using this tag, the object type is inferred from the tag, that is, it is not necessary to specify it explicitly:
// Then:
auto a = make_shared<very_very_very_long_type_name>(very_very_very_long_type_name{1, 3});
// Now:
auto b = make_shared(braces<very_very_very_long_type_name>, 1, 3);
The old overloads, of course, remain. The difference is that now we can explicitly choose the way of initialization.
Similarly it would work with functions that accept a set of parameters to pass to the constructor, for example, emplace_back.
Example to follow
This approach is not new for the standard library. A similar scheme is used with the tag std::in_place_type, for example, in the constructor of the std::variant class. This tag indicates that we need to call the constructor of the emplaced class. But in our case we go a little further. We provide the opportunity not just to signal that we need to call the constructor, but to tell which exactly constructor it's supposed to be.
Summary
The main point is to give user an opportunity to choose between the direct-initialization and the direct-list-initialization explicitly. There are many cases when it matters.
Given solution:
- Simplifies programmer's life and enhances the functionality of the standard library.
- Maintains full backward compatibility. Nothing is broken.
- Is quite general. It works with make_shared, make_unique, make_any, make_from_tuple, emplace and more.
- Seamlessly fits into the standard library and is pretty convenient to use.