In a Cppcon talk, the term "friend injection" was brought up, and the following code sample was provided:
using size_t = decltype(sizeof(int));
namespace detail {
template<auto> struct info { constexpr auto friend get(info); };
template<class T> struct type {
using value_type = T;
static constexpr size_t id{};
constexpr auto friend get(info<&id>) { return type{}; }
};
template<class T> auto remove_const(const T) -> T;
} // namespace detail
template<class T>
inline constexpr auto meta = &detail::type<T>::id;
using info = decltype(detail::remove_const(meta<void>));
template<auto meta>
using type_of = typename decltype(get(detail::info<meta>{}))::value_type;
int main() {
type_of<meta<int>> ans = 42;
return ans;
}
I have a few questions regarding this code:
Inside of the
type
struct, what is the purpose ofid
?What exactly is "friend injection" enabling here?
How is
get
being invoked here, seemingly in a context outside of bothinfo
andtype
?
In a Cppcon talk, the term "friend injection" was brought up, and the following code sample was provided:
using size_t = decltype(sizeof(int));
namespace detail {
template<auto> struct info { constexpr auto friend get(info); };
template<class T> struct type {
using value_type = T;
static constexpr size_t id{};
constexpr auto friend get(info<&id>) { return type{}; }
};
template<class T> auto remove_const(const T) -> T;
} // namespace detail
template<class T>
inline constexpr auto meta = &detail::type<T>::id;
using info = decltype(detail::remove_const(meta<void>));
template<auto meta>
using type_of = typename decltype(get(detail::info<meta>{}))::value_type;
int main() {
type_of<meta<int>> ans = 42;
return ans;
}
I have a few questions regarding this code:
Inside of the
type
struct, what is the purpose ofid
?What exactly is "friend injection" enabling here?
How is
get
being invoked here, seemingly in a context outside of bothinfo
andtype
?
1 Answer
Reset to default 4The example feels a bit overcomplicated. Here's a simpler one:
#include <iostream>
template <typename Key>
struct Reader
{
constexpr auto friend get(Reader<Key>);
};
template <typename Key, auto Value>
struct Writer
{
constexpr auto friend get(Reader<Key>) {return Value;}
};
struct MyKey1 {};
struct MyKey2 {};
int main()
{
(void)Writer<MyKey1, 42>{};
(void)Writer<MyKey2, 43>{};
std::cout << get(Reader<MyKey1>{}) << '\n'; // 42
std::cout << get(Reader<MyKey2>{}) << '\n'; // 43
}
The point of friend injection is that it gives you a compile-time map, where keys and values can be arbitrary types or values. I've chosen a single typename
key and an auto
value here, but it can be anything.
The map is write-only. New elements can be added at any time, but the previous one can't be erased. Unless you come up with yet another flag that marks an element as erased, or something like that.
Inside of the
type
struct what is is the purpose ofid
?
In your example, the address of id
acts as the key.
How is
get
being invoked here seemingly in a context outside of bothinfo
andtype
?
Via argument-dependent lookup.
What exactly is friend injection enabling here?
In this example, nothing useful.
In general it's used to smuggle information at compile-time, out of places that you normally can't. It's the basis of many advanced techniques.
For example, you can get the type of the current class in a macro, at class scope. If you play with it a bit, you'll notice that it's impossible to do without friend injection, as decltype(this)
only works inside of member functions. So we use it in a member function, store the type using friend injection, and then load it back outside of the function.
Another fun trick is reconstructing the inheritance hierarchy of a class. There, friend injection is used to detect failed overload resolution attempts (!!), which can be used to find the base classes if they're marked in a certain way.
id
is useless. – HolyBlackCat Commented Mar 19 at 17:14friend injection
is not code. ;) – Ulrich Eckhardt Commented Mar 19 at 18:38