Как обеспечить неявное и явное преобразование ctr для одного и того же типа?

Учитывая простой template <typename T> struct X { T x, y; };, я хочу предоставить конструкторы преобразования, чтобы пользователь мог написать:

X<double> a;
X<int16_t> b = a; // uses implicit conversion ctr (compiles with warning)
X<int16_t> c(a);  // uses explicit conversion ctr (compiles w/o warning)
X<int32_t> d = c; // uses implicit conversion ctr (compiles w/o warning)

Я считаю, что для достижения этой цели мне нужно реализовать как явный, так и явный конструктор преобразования из типа U. Но невозможно перегрузить "неявные" и explicit:

template <typename T> struct X {
     X(T x = T(), T y = T()) : x(x), y(y) {}

     // implicit conversion
     template <typename U> 
     X(const X<U>& other) : x(other.x), y(other.y) {}

     // not possible!
     template <typename U> 
     explicit X(const X<U>& other)
         : x(static_cast<T>(other.x))
         , y(static_cast<T>(other.y)) 
     {}

     T x, y;
};

Как я мог достичь этой цели (думаю, я не могу...)?

Моя первоначальная мысль заключалась в том, что Мне нужно включить/отключить ту или иную функцию в зависимости от is_lossless_convertible. Итак, есть ли подходящая черта характера?

Я хотел бы проверить, можно ли преобразовать скалярный тип U в тип T без потери точности:

using namespace std;
static_assert(is_lossless_convertible<int16_t, int32_t>::value == true);
static_assert(is_lossless_convertible<int32_t, int16_t>::value == false);
static_assert(is_lossless_convertible<int16_t, uint32_t>::value == false);
static_assert(is_lossless_convertible<int32_t, double>::value == true);
static_assert(is_lossless_convertible<double, int32_t>::value == false);

Короче говоря, он должен давать true, если std::is_convertible<U, T>::value == true и, если U x; T y = x; не выдаст предупреждение компилятора о потере информации.

Ответ 1

Вы не можете перегружать эксплойты, но вы можете предоставить явный конструктор преобразования и неявный оператор преобразования:

#include <iostream>
template<typename T> struct S {
   S() {}
   template<typename U> explicit S(const S<U> &) { std::cout << "explicit\n"; }
   template<typename U> operator S<U>() { return S<U>(this); }
private:
   template<typename U> friend struct S;
   template<typename U> S(const S<U> *) { std::cout << "implicit\n"; }
};

int main() {
   S<double> sd;
   S<int> si1(sd);
   S<int> si2 = sd;
}

Вывод:

explicit
implicit

Протестировано с помощью gcc и clang.

Явный конструктор преобразования предпочтительнее оператора неявного преобразования, потому что в последнем случае есть дополнительный (возможно, исключенный) вызов конструктору копирования S<int>.