Can static/dynamic/const/reinterpret_cast be used in unevaluated context?

巧了我就是萌 提交于 2019-12-07 11:47:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!