Reputation: 16711
How to determine (in the <type_traits>
spirit) whether or not one type explicitly convertible into another type? For example, I want to check the presence of F::explicit operator double const & () const;
for some class
/struct
F
, but, at the same time, F
should not been explicitly convertible to float
or long double
(something like pred< double const & >::value && !pred< float >::value && !pred< long double >::value
).
Note, that std::is_convertible< From, To >::value
checks "if From can be converted to To using implicit conversion". But I wish to determine whether there is the explicit conversion operator.
And, if it possible, the "how to determine, type From is convertible into a namely reference to type To?"?
Upvotes: 24
Views: 10694
Reputation: 2715
I'm answering this now to add a C++20-style update with concepts, and to give a small note on what the standard library has in terms of std::is_convertible
and std::convertible_to
. From the best of my knowledge std::is_convertible
checks for implicit-convertability, and std::convertible_to
tests for both implicit-convertability, and explicit-convertability. We can steal the second part of the concept implementation of std::convertible_to
to come up with something like:
template <class T, class U>
concept explicitly_convertible_to = requires(T t) { static_cast<U>(t); };
If you don't have c++20 concepts, this will work as well:
#include <type_traits>
template <class T, class U, class = void>
struct is_explicitly_convertible_to_impl : std::false_type {};
template <class T, class U>
struct is_explicitly_convertible_to_impl<
T, U, std::void_t<decltype(static_cast<U>(std::declval<T>()))>>
: std::true_type {};
template <class T, class U>
struct is_explicitly_convertible_to
: is_explicitly_convertible_to_impl<T, U> {};
template <class T, class U>
inline constexpr bool is_explicitly_convertible_to_v =
is_explicitly_convertible_to<T, U>::value;
For anyone wondering why this may be necessary for more detailed meta-programing: there are indeed situations where one type is explicitly, but not implicitly convertible to another. Chiefly: given an explicit
constructor, and a deleted assignment operator.
Upvotes: 6
Reputation: 61282
Your test set is at an off-site link and has varied since your original post, so I will copy verbatim the test-set I am talking about here:
static_assert(is_explicitly_convertible< double, double >::value, "1");
static_assert(is_explicitly_convertible< double &, double >::value, "2");
static_assert(is_explicitly_convertible< double const, double >::value, "3");
static_assert(is_explicitly_convertible< double const &, double >::value, "4");
static_assert(is_explicitly_convertible< double, double const & >::value, "5");
static_assert(is_explicitly_convertible< double &, double const & >::value, "6");
static_assert(is_explicitly_convertible< double const, double const & >::value, "7");
static_assert(is_explicitly_convertible< double const &, double const & >::value, "8");
static_assert(!is_explicitly_convertible< double, double & >::value, "9"); // not a ref
static_assert(is_explicitly_convertible< double &, double & >::value, "10");
static_assert(!is_explicitly_convertible< double const, double & >::value, "11");
static_assert(!is_explicitly_convertible< double const &, double & >::value, "12");
static_assert(is_explicitly_convertible< double, double const >::value, "13");
static_assert(is_explicitly_convertible< double &, double const >::value, "14");
static_assert(is_explicitly_convertible< double const, double const >::value, "15");
static_assert(is_explicitly_convertible< double const &, double const >::value, "16");
static_assert(is_explicitly_convertible< AA const &, A const & >::value, "=&1.a");
static_assert(is_explicitly_convertible< CC const &, C const & >::value, "=&1.b");
static_assert(is_explicitly_convertible< BB const &, B const & >::value, "=&1.c");
static_assert(!is_explicitly_convertible< AA const &, A & >::value, "&1.a");
static_assert(!is_explicitly_convertible< CC const &, C & >::value, "&1.b");
static_assert(!is_explicitly_convertible< BB const &, B & >::value, "&1.c");
static_assert(is_explicitly_convertible< AA const, A const & >::value, "=1.a");
static_assert(is_explicitly_convertible< CC const, C const & >::value, "=1.b");
static_assert(is_explicitly_convertible< BB const, B const & >::value, "=1.c");
//static_assert(!is_explicitly_convertible< AA const, A >::value, "=2.a"); // ???????????????
//static_assert(!is_explicitly_convertible< CC const, C >::value, "=2.b");
//static_assert(!is_explicitly_convertible< BB const, B >::value, "=2.c");
static_assert(!is_explicitly_convertible< AA const, A & >::value, "=3.a"); // good!
static_assert(!is_explicitly_convertible< CC const, C & >::value, "=3.b"); //
static_assert(!is_explicitly_convertible< BB const, B & >::value, "=3.c"); //
static_assert(!is_explicitly_convertible< AA const, A && >::value, "=4.a"); // not interesting
static_assert(!is_explicitly_convertible< CC const, C && >::value, "=4.b"); //
static_assert(!is_explicitly_convertible< BB const, B && >::value, "=4.c"); //
static_assert(!is_explicitly_convertible< AA const, B const & >::value, "=5.a");
static_assert(!is_explicitly_convertible< AA const, C const & >::value, "=5.b");
static_assert(!is_explicitly_convertible< BB const, A const & >::value, "=5.c");
static_assert(!is_explicitly_convertible< BB const, C const & >::value, "=6.a");
static_assert(!is_explicitly_convertible< CC const, A const & >::value, "=6.b");
static_assert(!is_explicitly_convertible< CC const, B const & >::value, "=6.c");
static_assert(!is_explicitly_convertible< AA const, B & >::value, "=7.a");
static_assert(!is_explicitly_convertible< AA const, C & >::value, "=7.b");
static_assert(!is_explicitly_convertible< BB const, A & >::value, "=7.c");
static_assert(!is_explicitly_convertible< BB const, C & >::value, "=8.a");
static_assert(!is_explicitly_convertible< CC const, A & >::value, "=8.b");
static_assert(!is_explicitly_convertible< CC const, B & >::value, "=8.c");
static_assert(!is_explicitly_convertible< AA const, B >::value, "=9.a"); // very subtle moment (see class AA above)
static_assert(!is_explicitly_convertible< AA const, C >::value, "=9.b");
static_assert(is_explicitly_convertible< BB const, A >::value == std::is_constructible< A, A && >::value, "=9.c"); // (see class BB above)
static_assert(!is_explicitly_convertible< BB const, C >::value, "=10.a");
static_assert(!is_explicitly_convertible< CC const, A >::value, "=10.b");
static_assert(!is_explicitly_convertible< CC const, B >::value, "=10.c");
static_assert(is_explicitly_convertible< AA, A & >::value, "~1.a");
static_assert(is_explicitly_convertible< CC, C & >::value, "~1.b");
static_assert(is_explicitly_convertible< BB, B & >::value, "~1.c");
//static_assert(!is_explicitly_convertible< AA, A >::value, "~2.a"); // ???????????????
//static_assert(!is_explicitly_convertible< CC, C >::value, "~2.b");
//static_assert(!is_explicitly_convertible< BB, B >::value, "~2.c");
static_assert(is_explicitly_convertible< AA, A const & >::value, "~3.a"); // convertible
static_assert(is_explicitly_convertible< CC, C const & >::value, "~3.b"); //
static_assert(is_explicitly_convertible< BB, B const & >::value, "~3.c"); //
static_assert(!is_explicitly_convertible< AA, B const & >::value, "~4.a");
static_assert(!is_explicitly_convertible< AA, C const & >::value, "~4.b");
static_assert(!is_explicitly_convertible< BB, A const & >::value, "~4.c");
static_assert(!is_explicitly_convertible< BB, C const & >::value, "~5.a");
static_assert(!is_explicitly_convertible< CC, A const & >::value, "~5.b");
static_assert(!is_explicitly_convertible< CC, B const & >::value, "~5.c");
static_assert(std::is_convertible< double, double const & >::value, "5*");
static_assert(!std::is_convertible< double, double & >::value, "9*");
If you avail of gcc 4.7.2 (and maybe earlier, I haven't checked) then the C++11 Standard Library solves the problem:
std::is_explicitly_convertible<From,To>
is defined in <type_traits>
However, you would then be availing of a error in that version of the C++11 Standard Library. This trait template shouldn't have been there, because it was removed from the Standard pursuant to N3047 (2010).
If you are on to gcc 4.8.1 (or maybe 4.8; I haven't checked) then this trait is no longer in the library, and if you want it you must again roll your own.
But it would only be human to inspect the definition in gcc 4.7.2's <type_traits>
for a start, and doing that reveals that the GNU implementer considered the trait
to be nothing but the inverse of std::is_constructible<To,From>
:
/// is_explicitly_convertible
template<typename _From, typename _To>
struct is_explicitly_convertible
: public is_constructible<_To, _From>
{ };
One may well think: But of course.
So why won't that do to be going on with? N3047 explains:
The remaining question is, in which way the also affected
is_explicitly_convertible
trait should be repaired. The basic choices are:
- Fix
is_explicitly_convertible
by returning to the current static_cast expression, no longer makingis_explicitly_convertible
dependent onis_constructible
.- Remove
is_explicitly_convertible
from the standard.The first choice was considered, but it turned out that there exist quite different understandings of what "explicitly convertible" should actually mean. While some believe that
static_cast
correctly expresses this, others believed that the fixedis_constructible
would provide a better meaning foris_explicitly_convertible
as well. Therefore this paper recommends to remove theis_explicitly_convertible
from the working draft. This should do no harm now, because nothing depends on that special definition yet.And if it turns out, that the trait would still be useful, it could be added in another revision of the standard.
There was no known bug in that definition, but there were opposed views as to whether the meaning of "explicitly convertible" that it codifies is the right one:-
From
is explicitly convertible to To
=df To
is constructible from From
From
is explicitly convertible to To
=df From
can be statically cast to To
I will not argue to this controversy (I haven't even worked out what the difference is, myself) but will suggest that you just pay your money and take your choice.
If you favour D1) then you can simply take the definition of the trait from
the gcc 4.7.2 <type_traits>
, as above.
If you favour D2, then you could plagiarize and adapt the definition of std::is_convertible<From,To>
from gcc 4.8.1 <type_traits>
. This definition invokes
internal auxiliary functions you would have to track down. The adaptation you
would want would be to change the SFINAE test for From
can be implicitly cast to
To
to a test for From
can be static_cast
to To
; and that would mean
replacing:
template<typename _From1, typename _To1>
static decltype(__test_aux<_To1>(std::declval<_From1>()), __one())
__test(int);
with:
template<typename _From1, typename _To1>
static decltype(__test_aux<_To1>(static_cast<_To1>(std::declval<_From1>())), __one())
__test(int);
A cut-down version of this definition (ignoring such cases as
From
being void
and To
being a function or array type) which would do
for the types that are tested in your static_assert
s:
template<typename From, typename To>
struct is_explicitly_convertible
{
template<typename T>
static void f(T);
template<typename F, typename T>
static constexpr auto test(int) ->
decltype(f(static_cast<T>(std::declval<F>())),true) {
return true;
}
template<typename F, typename T>
static constexpr auto test(...) -> bool {
return false;
}
static bool const value = test<From,To>(0);
};
Either the D1 definition or the cut-down D2 definition upholds all 63 of your static_assert
s.
(I compiled with g++ 4.7.2 and 4.8.1, -g;-O0;-Wall;-std=c++11
, and also with
the -std=gnu++1y
that you employed)
Your latest solution shows that you have made your own way to the D2 school, with a different style of implementation.
Of the two, until I find something wrong with it, I would prefer the D1 definition, as per gcc 4.7.2, merely because
it is much the simplest and in particular is a trivial derivative of
std::is_constructible
.
Upvotes: 14
Reputation: 5127
You need to define your own:
template <class U, class T>
struct is_explicitly_convertible
{
enum {value = std::is_constructible<T, U>::value && !std::is_convertible<U, T>::value};
};
Upvotes: 25