La motivación en sí misma se puede ver en el documento .
Es necesario hacer que los constructores sean condicionalmente explícitos. Es decir, quieres:
pair<string, string> safe() {
return {"meow", "purr"}; // ok
}
pair<vector<int>, vector<int>> unsafe() {
return {11, 22}; // error
}
Lo primero está bien, esos constructores están implícitos. Pero esto último sería malo, esos constructores lo son explicit
. Con C ++ 17 (o C ++ 20 con conceptos), la única forma de hacer que esto funcione es escribir dos constructores, uno explicit
y otro no:
template <typename T1, typename T2>
struct pair {
template <typename U1=T1, typename U2=T2,
std::enable_if_t<
std::is_constructible_v<T1, U1> &&
std::is_constructible_v<T2, U2> &&
std::is_convertible_v<U1, T1> &&
std::is_convertible_v<U2, T2>
, int> = 0>
constexpr pair(U1&&, U2&& );
template <typename U1=T1, typename U2=T2,
std::enable_if_t<
std::is_constructible_v<T1, U1> &&
std::is_constructible_v<T2, U2> &&
!(std::is_convertible_v<U1, T1> &&
std::is_convertible_v<U2, T2>)
, int> = 0>
explicit constexpr pair(U1&&, U2&& );
};
Estos están casi completamente duplicados, y las definiciones de estos constructores serían idénticas.
Con explicit(bool)
, puede escribir un solo constructor, con la parte condicionalmente explícita de la construcción localizada solo en el explicit
especificador:
template <typename T1, typename T2>
struct pair {
template <typename U1=T1, typename U2=T2,
std::enable_if_t<
std::is_constructible_v<T1, U1> &&
std::is_constructible_v<T2, U2>
, int> = 0>
explicit(!std::is_convertible_v<U1, T1> ||
!std::is_convertible_v<U2, T2>)
constexpr pair(U1&&, U2&& );
};
Esto coincide mejor con la intención, es mucho menos código para escribir y es menos trabajo para el compilador durante la resolución de sobrecarga (ya que hay menos constructores para elegir).
tuple
con esta característica.