问题
I tried to provide structures for checking is A
is (choose cast)-castable to B
.
All four casts would have exact same implementation, expect their names (local-macro-able definitions would be possible, but not necessary). I wrote many check-for operators structures, for example:
#include <iostream>
#include <type_traits>
#include <string>
template<class, class, class, class = void>
struct is_valid_ternary_operator : std::false_type
{ };
template<class T, class S, class R>
struct is_valid_ternary_operator <T, S, R,
std::void_t<decltype(std::declval<T>() ?
std::declval<S>() :
std::declval<R>())>> : std::true_type
{ };
int main()
{
//true? 1 : 0 //is ok
std::cout << is_valid_ternary_operator<bool, int, int>::value << std::endl;
//true? 1 : std::string("0") //would be error
std::cout << is_valid_ternary_operator<bool, int, std::string>::value << std::endl;
//true? std::string("1") : std::string("0") //ok
std::cout << is_valid_ternary_operator<bool, std::string, std::string>::value << std::endl;
//std::string("1")? 1 : 0 //error
std::cout << is_valid_ternary_operator<std::string, int, int>::value << std::endl;
}
Live example
Expected output is displayed. But now consider doing the same with casts:
template<class T, class S, class = void>
struct is_static_cast_able : std::false_type
{ };
template<class T, class S>
struct is_static_cast_able<T, S,
std::void_t<decltype(static_cast<std::declval<S>()>
(std::declval<T>()))>> : std::true_type
{ };
But it generates errors:
main.cpp:12:84: error: template argument 1 is invalid
(std::declval<T>()))>> : std::true_type
^~
main.cpp:12:94: error: template argument 3 is invalid
(std::declval<T>()))>> : std::true_type
^~~~~~~~~
Live
Is using casts in unevaluated context not allowed?
回答1:
Is using casts in unevaluated context not allowed?
Short answer: yes, it is allowed.
Anyway, I would try to reduce the example to a minimal, working one.
Note that casts are expressions and they are expected to work with operators the operands of which are unevaluated (sizeof
, noexcept
, decltype
, typeid
), unless explicitly stated the opposite.
As an example, sizeof
is an unevaluated context:
int main() {
unsigned int i;
sizeof(static_cast<int>(i));
}
Far easier an example, and it works.
The same can be shown using decltype
, the operands of which are unevaluated as well:
int main() {
unsigned int i;
decltype(static_cast<int>(i)) j = i;
}
And so on, we can do something similar both with noexcept
:
int main() {
unsigned int i;
bool b = noexcept(static_cast<int>(i));
}
And with typeid
:
#include <typeinfo>
int main() {
unsigned int i;
auto b = typeid(static_cast<int>(i)).name();
}
回答2:
is_static_castable<T, S>
sounds like something already existing in the C++ standard library. as std::is_convertible.
And to your question: static_cast
doesn't always mean compile time. For example, in the conversions of integer and floating point types, the compiler may chose to emit code for the conversion if it cannot do so at compile-time. Though. it can very much appear in an unevaluated context.
Unevaluated operands in C++ are very limited: The contexts are:
decltype
sizeof
typeid
noexcept
To quote C++ standard draft, [expr/8]
In some contexts, unevaluated operands appear (
[expr.typeid]
,[expr.sizeof]
,[expr.unary.noexcept]
,[dcl.type.simple]
). An unevaluated operand is not evaluated. An unevaluated operand is considered a full-expression.
NOTE: std::is_convertible
doesn't cover all cases of static_cast
Therefore, for completeness, this is what you want to do with is_static_cast_able
, covering virtually all cases of static_cast
:
template<class T, class S, class = void>
struct is_static_cast_able : std::false_type
{ };
template<class T, class S>
struct is_static_cast_able<T, S,
std::enable_if_t<
std::is_convertible<T, S>::value ||
std::is_base_of<std::decay_t<T>, std::decay_t<S>>::value ||
std::is_base_of<std::decay_t<S>, std::decay_t<T>>::value ||
std::is_base_of<std::remove_pointer_t<T>, std::remove_pointer_t<S>>::value ||
std::is_base_of<std::remove_pointer_t<S>, std::remove_pointer_t<T>>::value ||
(std::is_same<T, void*>::value && std::is_pointer<S>::value) ||
(std::is_same<S, void*>::value && std::is_pointer<T>::value)
>
>
: std::true_type { };
The following test code passes:
struct A {};
struct B : public A {};
int main()
{
static_assert(is_static_cast_able<int, char>::value, "");
static_assert(is_static_cast_able<double, int>::value, "");
static_assert(is_static_cast_able<int, double>::value, "");
static_assert(is_static_cast_able<void*, double*>::value, "");
static_assert(is_static_cast_able<double*, void*>::value, "");
static_assert(is_static_cast_able<B, A>::value, "");
static_assert(is_static_cast_able<A, B>::value, "");
static_assert(is_static_cast_able<B&, A&>::value, "");
static_assert(is_static_cast_able<B*, A*>::value, "");
static_assert(is_static_cast_able<A&, B&>::value, "");
static_assert(is_static_cast_able<A*, B*>::value, "");
}
See it Live on Coliru
来源:https://stackoverflow.com/questions/39669783/can-static-dynamic-const-reinterpret-cast-be-used-in-unevaluated-context