Black Moses Black Moses - 2 months ago 11
C++ Question

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

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?

Answer

Is using casts in unevaluated context not allowed?

It is allowed, but I would try to reduce the example to a minimal, working one.
Note that casts are expressions, so they are expected to work with operators the operands of which are unevaluated (sizeof, noexcept, decltype, typeid), if not 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();
}