Re: Type-traits may have a hole as far as explicit-convertibility
is concerned.
Am 10.06.2012 07:02, schrieb Daryle Walker:
I decided to make the conversion operator only work when the
conversion from T to U is implicit. So I added an SFINAE condition:
//...
#include <type_traits>
#include <utility>
//...
template <
typename U,
typename std::enable_if<
std::is_convertible<T, U>::value,
std::nullptr_t
>::type...
>
constexpr
operator MyArray<U,N>() const;
//...
I used the index_tuple trick to implement this member function
template, returning a brace-initializer expression.
What is a "brace-initializer expression"? Do you mean a braced-init-list
such as
{ /expression-list/ };
? This is *no* expression.
I also wonder why you added the std::nullptr_t argument. You could just
remove it, because std::enable_if has a default template type of void.
Then I decided
to extend this with a second conversion operator, this time for
types that can convert non-implicitly:
//...
template <
typename V,
typename std::enable_if<
!std::is_convertible<T, V>::value
&& std::is_constructible<V, T const &>::value,
std::nullptr_t
>::type...
>
explicit constexpr
operator MyArray<V,N>() const;
//...
The internal code is just like the first operator, but the
initializers around surrounded by "static_cast<V>" before variadic
expansion.
Well yes, static_cast is different from a direct-initialization. Btw.
you don't even need a class enum for this. The same kind of static_cast
conversion is necessary when you convert a classic (i.e. unscoped)
enumeration type to arithmetic type.
Now I needed to test this code with two types that convert, but not
implicitly. (I've been using the built-in arithmetic types, but all
conversions between them are implicit, even the narrowing ones!) I
thought making a custom class-type type with explicit constructors
and/or operators for this, but decided to go with the new-fangled
enum-class type, which don't have the implicit conversions to int
that classic enums do.
I hit a wall, but it was a good thing I did it, since I would have
missed a use case if I used a class-type.
When converting between an enum-class and a built-in numeric type,
you need to explicitly write a "static_cast":
//...
enum class two_bit_t
: unsigned
{ zero, one, two, three };
two_bit_t const v = two_bit_t::two;
unsigned const vv = v; // ERROR
unsigned const vv{ v }; // ERROR
auto const vv = static_cast<unsigned>( v ); // WORKS
//...
My tests had a conversion between MyArray<two_bit_t,N> and
MyArray<unsigned,N>, but it never triggered. After trying many
guesses, I got this to work:
//...
// Comment out BOTH of the previous conversion operators
template < typename W >
constexpr
operator MyArray<W,N>() const;
//...
This operator template called the internal function that used a
"static_cast" wrapper. But why didn't the previous version work?
Because static_cast covers implicit conversions; conversions that
can use the constructor(-like) syntax, which std::is_constructible
covers; plus several others! Enum-class to built-in integer is one
of those other conversions.
Correct. Another situation is a cast from void* to int*, or the
conversion of Base& to Derived&.
I can't use SFINAE to restrict conversions just to explicitly-
convertible types; I have to make a general version (or general
minus implicit) and just have the compiler choke within the
conversion function when it encounters two types with no static_cast
path, instead of having it error-out in advance via SFINAE.
(Does anyone know of a better workaround?)
What you are asking for is a trait like is_static_castable. This is
really very easy to implement, e.g. like so:
#include <utility>
struct is_static_castable_impl
{
template<class From, class To, class =
decltype(static_cast<To>(std::declval<From>()))
>
static std::true_type test(int);
template<class, class>
static std::false_type test(...);
};
template<class From, class To>
struct is_static_castable :
decltype(is_static_castable_impl::test<From, To>(0))
{
};
The problem for us is that there is NO type-trait class template
that covers explicit conversions that only have the static_cast
operator as their only path (besides C-casts). We should add this,
and the trivial & no-throw variants, to the TS2 (or whatever we're
calling the next minor update to C++).
I weakly agree, because this trait is really easy to implement, see
above. I used is_static_castable internally to implement
is_constructible as a pure library-based emulation, because the hard
part of implementing is_constructible is the two-argument-case. This is
so, because the most natural way to emulate this is to use a functional
cast using an expression of the form T(std::declval<U>()), but this is
equivalent to the "C cast": (T) std::declval<U>(). is_static_castable
helps to filter away the otherwise valid reinterpret_cast and const_cast
interpretations, so you can then - in a second step - filter away the
remaining cases that are unique for static_cast compared to direct
initialization.
Before adding is_static_castable and it's nothrow/trivially variants I
rather would prefer to see is_nothrow_convertible and
is_trivially_convertible, which are really missing given the
corresponding is_XX_constructible traits.
Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]