Reputation: 23527
The deduction guide for std::array
requires all types be the same:
std::array arr = { 1, 2, 3.4 }; // error
What is the rationale behind such a requirement? Would there be any significant drawback if different types were allowed instead? For example:
namespace std {
template <typename... T>
array(T...) -> array<std::common_type_t<T...>, sizeof...(T)>;
}
std::array arr = { 1, 2, 3.4 }; // decltype(arr)::value_type deduced as double
Upvotes: 7
Views: 1060
Reputation: 63142
It matches how function template arguments are deduced.
e.g.
template<typename T>
void foo(T, T){}
template<typename T>
struct bar{ bar(T, T) {} };
int main()
{
foo(1, 1.5); // error, note: deduced conflicting types for parameter 'T' ('int' and 'double')
bar(1, 1.5); // error, note: deduced conflicting types for parameter 'T' ('int' and 'double')
}
But you can provide a deduction guide for common types.
template<typename T>
struct baz{ baz(T, T) {} };
template<typename T, typename U>
baz(T, U) -> baz<std::common_type_t<T, U>>
or overloads that forward to common types
template<typename T>
void quux(T, T){}
template<typename T, typename U>
std::enable_if_t<!std::is_same<std::decay_t<T>, std::decay_t<U>>> quux(T t, U u)
{
using C = std::common_type_t<T, U>;
quux<C>(std::forward<C>(t), std::forward<C>(u)); // I think this is right
}
int main()
{
baz(1, 1.5);
quux(1, 1.5);
}
Upvotes: 6
Reputation: 4570
Edit: In a comment below, Daniel points out he was really asking about why a std::array
of one single type could not be initialized by a list containing entries of different types (for example two int
and a double
). Not why a std::array
has to be one single type.
It doesn't address Daniel's request for a rationale but some C++ environments support the experimental\array header and the std::experimental::make_array
class which does allow initialisation of a std::array from different types. The array type can be deduced or specified...
#include <array>
#include <experimental/array>
// creates array of doubles
auto arr = std::experimental::make_array(1, 2, 3.4);
// creates array of ints
auto ra = std::experimental::make_array<int> (1, 2, 3.4);
What is the rationale behind such a requirement?
The restriction is required to meet the semantics of the array container.
For example, cppreference puts it like this …
This container is an aggregate type with the same semantics as a struct holding a C-style array
T[N]
as its only non-static data member.
Obviously there's no way to have a C-style array of more than one type T.
For another perspective: cplusplus says this …
Container properties
Sequence
Elements in sequence containers are ordered in a strict linear sequence. Individual elements are accessed by their position in this sequence.Contiguous storage
The elements are stored in contiguous memory locations, allowing constant time random access to elements. Pointers to an element can be offset to access other elements.Fixed-size aggregate
The container uses implicit constructors and destructors to allocate the required space statically. Its size is compile-time constant. No memory or time overhead.
For pointer arithmetic to make sense every element has to be the same width. For dereferencing the pointer (after pointer arithmetic) to make sense each element has to use the same bit representation - otherwise you might find yourself treating an IEEE floating point representation as if it were a two's-complement integer (or vice versa).
It doesn't really address your request for a rationale to say "because the standard says so" but it does (I think). I can't find a citeable copy of the actual standard but a working draft of the C++ standard said this...
26.3.7.2 array constructors, copy, and assignment [array.cons]
The conditions for an aggregate (11.6.1) shall be met. Class array relies on the implicitly-declared special member functions (15.1, 15.4, and 15.8) to conform to the container requirements table in 26.2. In addition to the requirements specified in the container requirements table, the implicit move constructor and move assignment operator for array require that T be MoveConstructible or MoveAssignable, respectively.template<class T, class... U> array(T, U...) -> array<T, 1 + sizeof...(U)>;`
Requires:
(is_same_v<T, U> && ...)
is true. Otherwise the program is ill-formed.
Upvotes: 1
Reputation: 137415
There are substantial design issues with using common_type
. For example, std::common_type_t<A, B, C>
, std::common_type_t<C, A, B>
and std::common_type_t<C, B, A>
need not all exist - and if they do, need not be the same type:
struct A;
struct B;
struct C;
struct A { operator B(); };
struct B { operator C(); };
struct C { operator A(); };
static_assert(std::is_same_v<std::common_type_t<A, B, C>, C>);
static_assert(std::is_same_v<std::common_type_t<C, A, B>, B>);
static_assert(std::is_same_v<std::common_type_t<C, B, A>, A>);
That makes for an "interesting" user experience when reordering the elements of the initializer causes a different type to be deduced (or an error to be emitted).
Upvotes: 10