Search code examples
c++stdtupleentt

entt basic_registry::get<>() explanation


Currently using g++11.3.0, C++20.

Could anyone explain how the basic_registry's get<>() template function is able to function such that when retrieving a single component through a call like auto& comp {registry.get<A>()};, the component itself can be directly assigned to and/or accessed like a normal reference, and when retrieving multiple components through a call like registry.get<A, B, C>(), it can be unpacked via a structured binding auto& [a, b, c].

Code (from https://skypjack.github.io/entt/registry_8hpp_source.html)

template<typename... Type>
[[nodiscard]] decltype(auto) get([[maybe_unused]] const entity_type entt) {
    if constexpr(sizeof...(Type) == 1u) {
        return (const_cast<Type &>(std::as_const(*this).template get<Type>(entt)), ...);
    } else {
        return std::forward_as_tuple(get<Type>(entt)...);
    }
}

Solution

  • The following function template returns a tuple:

    template <typename ...T>
    auto foo() {
        return std::make_tuple(T{}...);
    }
    

    You can call it via

    auto [a, b, c] = foo<int,int,double>();
    

    The following function template returns just a T:

    template <typename T>
    auto bar() { return T{};}
    

    you can call it via

    auto d{bar<int>()};
    

    Functions can have only one return type. foo and bar are function templates. The functions foo<int,int,double> and bar<int> have one return type. They return one value.


    The following function template selects between two function templates to be instantiated and called depending on the number of template arguments:

    template <typename ...T>
    auto moo() {
        if constexpr(sizeof...(T) == 1u) { return bar<T...>(); }
        else return foo<T...>();
    }
    

    You can call it via:

    auto [x,y,z] = moo<int,int,double>();
    

    Or

    auto w{ moo<int>()};
    

    Note that when T... is more than a single argument then bar<T...> would be a mismatch (because bar has only a single argument. However, with constexpr if in template context the not taken branch is discarded at compile time.


    In the case of sizeof...(T) == 1u the parameter pack is expanded. However, the expansion is only a single type in this case (otherwise bar<T...> would not compile). It could have been written as

    template <typename T,typename ...More>
    auto moo() {
        if constexpr(sizeof...(More) == 0u) { return bar<T>(); }
        else return foo<T,More...>();
    }
    

    Live Demo