Is there any way of abbreviating long template parameter lists in C++?

3 weeks ago 25
ARTICLE AD BOX

Three things come to mind:

1. Deduction

When invoking (overloaded) template functions, template argument deduction will deduce the free types. Presumably, you're already aware of this part (because you are using variadics in your example).

c++17 CTAD extends this to constructors of types, e.g. std::vector { 1,2,3 } becomes std::vector<int, std::allocator<int> >, std::array { 1,2,3 } becomes std::array<int, 3>.

2. Template Aliases:

You can either use template aliases:

template <typename K, typename V, typename Pair = std::pair<K const, V> > using MyMap = boost::container::flat_map<K, V, std::less<K>, boost::container::small_vector<Pair, 10> >;

Now you can use the unwieldy type name as

MyMap<std::string, double> m;

3. Opaque types / Concepts

In reality, you don't usually care about all the subsidiary details. For example, you might only require a "Range", or a "Container" or a "Map", not some specific instance.

Instead of

template <typename TA, size_t NA> struct Address { std::array<TA, NA> details; }; template <typename TO, size_t NO> struct Owner { std::array<TO, NO> details; }; template <typename... Ts> struct HouseRoomsLayout { std::tuple<Ts...> data_; }; template <typename TA, size_t NA, typename TO, size_t NO, typename... Ts> struct House { Address<TA, NA> address_; Owner<TO, NO> owner_; HouseRoomsLayout<Ts...> layout_; };

You could write

template <typename TA, size_t NA> struct Address { std::array<TA, NA> details; }; template <typename TO, size_t NO> struct Owner { std::array<TO, NO> details; }; template <typename... Ts> struct HouseRoomsLayout { std::tuple<Ts...> data_; HouseRoomsLayout(Ts const&... room) : data_(room...) {} }; template <class T, class... U> Address(T, U...) -> Address<T, 1 + sizeof...(U)>; template <class T, class... U> Owner(T, U...) -> Owner<T, 1 + sizeof...(U)>; template <typename Address, typename Owner, typename Layout> struct House { Address address_; Owner owner_; Layout layout_; }; auto create_house_from(auto address, auto owner, auto... rooms) { std::cout << __PRETTY_FUNCTION__ << std::endl; return House{address, owner, HouseRoomsLayout{std::move(rooms)...}}; } int main() { Address addr{"Hello Inc"s, "World Drive 42", "Cincinatti", "Uganda"}; Owner owner{999}; // imagining an ID type here struct Room { std::vector<int> furniture; } backroom; struct Garage { std::vector<int> tools; } garage; HouseRoomsLayout layout{ backroom, garage }; auto h = create_house_from(addr, owner, layout); return 0; }

Better yet, you wouldn't need create_XXX_from anymore, since that's what CTAD was invented for:

auto h = House{addr, owner, layout};

creates the exact same house. In fact, go ham!

Live On Coliru

#include <array> #include <string> #include <tuple> #include <vector> using namespace std::string_literals; template <typename TA, size_t NA> struct Address { std::array<TA, NA> details; }; template <typename TO, size_t NO> struct Owner { std::array<TO, NO> details; }; template <typename... Ts> struct HouseRoomsLayout { std::tuple<Ts...> data_; HouseRoomsLayout(Ts const&... room) : data_(room...) {} }; template <class T, class... U> Address(T, U...) -> Address<T, 1 + sizeof...(U)>; template <class T, class... U> Owner(T, U...) -> Owner<T, 1 + sizeof...(U)>; template <typename Address, typename Owner, typename Layout> struct House { Address address_; Owner owner_; Layout layout_; }; int main() { struct Room { std::vector<int> furniture; } backroom; struct Garage { std::vector<int> tools; } garage; auto h = House{ Address{"Hello Inc"s, "World Drive 42", "Cincinatti", "Uganda"}, Owner{999}, HouseRoomsLayout{backroom, garage}, }; }

I'm assuming you're learning about things here. The presented design does not seem like good interface design. At the very least, if you need all that flexibility, make concepts for the different classes of types.

Read Entire Article