std::make_array uses the common type. However, my main problem with this is that it allows code to compile that would otherwise not compile:
template <typename T>
void foo(T a, T b)
{
}
int main()
{
auto arr = std::experimental::make_array(1, 3.0); // Compiles
foo(1, 3.0); // a and b types must match
std::array<int, 2> arr2{1, 3.0}; // narrowing conversion
}
This is not necessarily a bad thing. But I find it to be inconsistent. Note that due to how template argument deduction works, std::make_array<int>
actually disables deduction (because you specified a type explicitly) so that does not solve the problem.
What is the rationale behind using common type here?
std::make_array
uses the common type.
What else should it use to deduce the type of the elements of the to-be-created array?
The first element? If so, wouldn't it be very inconsistent to have ...
auto a = make_array(1, 3.0);
auto b = make_array(3.0, 1);
... a different type of array for those two?
Sure, one could SFINAE or static_assert
that all types of the passed arguments to std::make_array
are the same, but then you artificially restrict the use of that function a lot. Consider:
short x = 3;
auto c = make_array(x, 42); // Should this be a compiler error? I'd say no ...
You can go further and loosen the requirement of the types being the "same" to types that are (in some sense) compatible. But then, aren't you already more than halfway at the std::common_type
...?
The (probably) official reasoning (N3824) is that the intention is to provide both a "tuple-like" interface as well as an "array-like" interface. Where the former acts like std::make_tuple
or std::make_pair
and deduces some suitable (thus common) type, the later reflects raw array declarations like ...
int arr[] = {1, 2, 3};
... which do not deduce the type of the elements but rather only the array length.
It's not a direct answer to your question, but a possible solution for what you want.
You could solve it by implementing your own function helper. There are some tricks to enforce the strict narrowing conversion and not use a common type to deduce the elements' type. One could implement it by checking std::is_same
for each argument:
template <typename T, typename... Args>
constexpr auto make_array(Args&&... args) -> std::array<T, sizeof...(Args)>
{
static_assert((std::is_same<std::remove_cv_t<T>, std::decay_t<Args>>{} && ...),
"arguments shall be of same type.");
return {{std::forward<Args>(args)...}};
}
int main()
{
auto a = make_array<int>(1, 2, 3);
// ↓ error: static_assert failed "arguments shall be of same type."
//auto b = make_array<int>(1, 2, 3.f);
}
Values 1
, 2
and 3
are of type int
, so the check passes. If you try to pass e.g. a value of type float
, the static assertion won't let it compile, as is_same<int, float>
resolves to false.
The only difference from std::make_array
here is that you are required to provide the type of your elements (e.g. make_array<int>
), so it won't deduce a common type based upon your arguments. Only size will be deduced from the arguments.
There can't be narrowing conversions here too, as the arguments' type all have to be exact the same as the one you provided.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With