Hypothetically, If I were creating a point class and I wanted it to deduced the type based on the arguments I would want it to promote the point class to the highest argument. For example:
template <class dtype>
class Point;
...
auto x = Point(1, 1.0); // Point<double> specialized
auto y = Point(1.0, 1); // Point<double> specialized
I am not sure how to achieve this within a constructor. I've been able to make it deduce the type from a function which calls an explicitly specialized constructor, but not from the constructor itself.
Here is my attempt so far:
#include <type_traits>
template <typename... Ts>
struct promoted_type_wrap;
template <typename T>
struct promoted_type_wrap<T> {
using type = T;
};
template <typename T, typename U, typename... Ts>
struct promoted_type_wrap<T, U, Ts...> {
using type = typename promoted_type_wrap<typename std::conditional<
(sizeof(U) <= sizeof(T)), T, U >::type, Ts... >::type;
};
template <typename... Ts>
using promoted_type = typename promoted_type_wrap<Ts...>::type;
template <typename T>
using same_type = typename promoted_type_wrap<T>::type;
template <class dtype>
class Point {
protected:
dtype x, y;
public:
constexpr Point(const dtype x, const same_type<dtype> y)
: x(x), y(y) {
}
};
template <class dtype, class etype>
constexpr auto make_Point(const dtype x, const etype y) {
return Point<promoted_type<dtype, etype>>(x, y);
}
void test() {
constexpr auto x = make_Point(1, 2.0); // Point<double> specialized
constexpr auto y = make_Point(1.0, 2); // Point<double> specialized
constexpr auto z = Point(1, 2.0); // Point<int> specialized
constexpr auto w = Point(1.0, 2); // Point<double> specialized
}
It makes sense why Point(1, 2.0)
is specialized as a Point<int>
since the first argument is an int
which forces the second argument in the constructor as an int
; however, I am unsure how to go about rewriting the constructor to behave like the pseudo-constructor factory.
however, I am unsure how to go about rewriting the constructor to behave like the pseudo-constructor factory.
Not the constructor: you have to write a custom deduction guide.
Something as follows
template <typename T1, typename T2>
Point(T1, T2) -> Point<promoted_type<T1, T2>>;
The following is a full compiling example
#include <type_traits>
template <typename... Ts>
struct promoted_type_wrap;
template <typename T>
struct promoted_type_wrap<T>
{ using type = T; };
template <typename T, typename U, typename... Ts>
struct promoted_type_wrap<T, U, Ts...>
{ using type = typename promoted_type_wrap<std::conditional_t<
(sizeof(U) <= sizeof(T)), T, U >, Ts... >::type; };
template <typename... Ts>
using promoted_type = typename promoted_type_wrap<Ts...>::type;
template <typename dtype>
class Point
{
protected:
dtype x, y;
public:
template <typename T1, typename T2>
constexpr Point (T1 const & a, T2 const & b) : x(a), y(b)
{ }
};
template <typename T1, typename T2>
Point(T1, T2) -> Point<promoted_type<T1, T2>>;
int main ()
{
constexpr auto z = Point(1, 2.0); // now Point<double>
constexpr auto w = Point(1.0, 2); // again Point<double>
static_assert( std::is_same_v<decltype(z), Point<double> const> );
static_assert( std::is_same_v<decltype(w), Point<double> const> );
}
Off Topic: I don't think it's a good idea select the "promoted type" according the size of the type as in your
template <typename T, typename U, typename... Ts>
struct promoted_type_wrap<T, U, Ts...>
{ using type = typename promoted_type_wrap<std::conditional_t<
(sizeof(U) <= sizeof(T)), T, U >, Ts... >::type; };
Even ignoring other problems you have that, when you have different types of the same size, the selected type is the first one.
By example, in my platform both g++ and clang++ have sizeof(long) == sizeof(float)
, so we get that
constexpr auto z = Point(1l, 2.0); // <-- deduced as Point<long>
constexpr auto w = Point(1.0, 2l); // <-- deduced as Point<double>
static_assert( std::is_same_v<decltype(z), Point<long> const> );
static_assert( std::is_same_v<decltype(w), Point<double> const> );
I suggest the use of something that select the "preferred type" independently from the order of the types.
It seems to me that you should use std::common_type
as follows
#include <type_traits>
template <typename dtype>
class Point
{
protected:
dtype x, y;
public:
template <typename T1, typename T2>
constexpr Point (T1 const & a, T2 const & b) : x(a), y(b)
{ }
};
template <typename T1, typename T2>
Point(T1, T2) -> Point<std::common_type_t<T1, T2>>;
int main ()
{
constexpr auto z = Point(1l, 2.0); // <-- deduced as Point<double>
constexpr auto w = Point(1.0, 2l); // <-- deduced as Point<double>
static_assert( std::is_same_v<decltype(z), Point<double> const> );
static_assert( std::is_same_v<decltype(w), Point<double> const> );
}