Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rationale for std::make_array common type

Tags:

c++

c++17

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?

like image 688
user7869963 Avatar asked Oct 30 '22 09:10

user7869963


2 Answers

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.

like image 183
Daniel Jour Avatar answered Nov 15 '22 07:11

Daniel Jour


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.

like image 42
Mário Feroldi Avatar answered Nov 15 '22 05:11

Mário Feroldi