Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Materializing" an object of a known type for C++ type inference

The following code does everything I need from it in every C++11 and later compiler I have tried. So, in practice, for my purposes, it works (and is expected to work, at least on Linux, in the foreseeable future, due to historical reasons). However, from a language lawyer perspective, this code is invalid, as it contains a construct (dereference of a pointer to a nonexistent object) that is formally an UD, even though this dereference is actually never executed.

#include <type_traits>
#include <iostream>

namespace n1 {

struct int_based { int base = 0; };
inline int get_base(int_based ib) { return ib.base; }

}

namespace n2 {

struct double_based;
double get_base(const double_based&);

}

template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));

int main() {
    auto isInt = std::is_same<base_type<n1::int_based>, int>::value;
    auto isDouble = std::is_same<base_type<n2::double_based>, double>::value;
    auto unlike = std::is_same<base_type<n1::int_based>, double>::value;
    std::cout << isInt << isDouble << unlike << std::endl;
    return 0;
}

The code does Koenig lookup for type mapping and infers the mapped type using function signatures that I would not want to change. In this example, the double_based type is incomplete; in my real use cases, the types are expected to be complete but are not guaranteed to be DefaultConstructible. The actual code is a part of a type-safe serialization logic.

The question is: is there a standard-compliant way of "materializing" an object of the template parameter type T for use in decltype in this code, or is it impossible to have such standard-complying type mapping without having a preconstructed object of the source type?

Replacing function parameters with pointers to the objects is ugly and doesn't really solve the problem, as it is unclear what these functions need to do with the nullptr argument in the general case without introducing yet another UB.

like image 219
Kit. Avatar asked Nov 26 '25 03:11

Kit.


2 Answers

What you are looking for is std::declval. It is a function that returns the type you give it so you can work with an object of that type in a unevaluated context. That turns

template <typename T>
using base_type = decltype((get_base(*(T*)nullptr)));

into

template <typename T>
using base_type = decltype((get_base(std::declval<T>())));

Do note that there is no requirement of having a definition for std::declval. If you try to use

T foo = std::declval<T>();

then your program is ill-formed.

like image 196
NathanOliver Avatar answered Nov 27 '25 16:11

NathanOliver


Actually, this code is fine. (It is true that it is UB to dereference a null pointer and bind the result to a reference, or to dereference a null pointer and access the resulting lvalue. However, when the execution of the program does not actually evaluate these constructs, there is no UB.)

But it is true that std::declval<T>() is the preferred idiom. Unlike the null pointer trick, std::declval<T>() is "safe": if you accidentally use it in a potentially evaluated context, there will be a compile-time error. It is also much less ugly.

like image 39
Brian Bi Avatar answered Nov 27 '25 15:11

Brian Bi



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!