Skip to content

AlgoSkyNet/conceptrodon

 
 

Repository files navigation

Conceptrodon

A C++20 metaprogramming library focusing on metafunction composition.

Prologue

Both boost::mp11 and kvasir::mpl contain a metafunction called 'compose.' It takes in a list of templates and a list of initiators. The function instantiates the first template and uses the result to instantiate the second one, then uses the result to instantiate the third one, and so on. Namely, mp_compose<F1, F2, …​, Fn>::fn<T…​> is Fn<…​F2<F1<T…​>>…​>.

However, since we don't have a universal template parameter representation in the language, the template heads of the parameters must be specified. Regarding boost::mp11 and kvasir::mpl, F1, F2, …​, Fn must be templates that accept only type arguments, meaning the universal template head of F1, F2, …​, Fn is template<typename...>. Therefore, many metafunctions in both libraries are not composable.

Conceptrodon restricts the template heads of metafunctions to be 'conformed' so that mixed 'primary signatures' are not allowed in the metafunctions' parameter lists. In the following example, the template UnconformedMetafunction is not permitted since its parameter list contains different 'primary signatures': type, auto, template<typename...>, and template<auto...>.

template<typename, auto, template<typename...> class, typename<auto...> class>
struct UnconformedMetafunction {};

To make UnconformedMetafunction conformed, we deploy member templates to accept each kind of parameter.

template<typename...>
struct ConformedMetafunction
{
    template<auto...>
    struct ProtoPage
    {
        template<template<typename...> class...>
        struct ProtoRoad
        {
            template<template<auto...> class...>
            struct ProtoRail
            { static constexpr bool value {true}; };

            template<template<auto...> class...Sequences>
            using Rail = ProtoRail<Sequences...>;
        };

        template<template<typename...> class...Containers>
        using Road = ProtoRoad<Containers...>;
    };

    template<auto...Variables>
    using Page = ProtoPage<Variables...>;
};

The reason for the complexity is explained here.

Overall, this library utilizes member templates to take arguments with different template heads in steps, allowing metafunctions to be composed naively.

For example, since every member template with the template-head template<typename...> is named Page and every member template with the template-head template<template<typename...> class...> is named Rail, every Page can be accepted by every Rail syntax-wise.

static_assert
(
    ConformedMetafunction<>::Page<>::Road<>::Rail
    <
        ConformedMetafunction<>::Page
    >
    ::value
);

Run this snippet on Godbolt.

Structure

Metafunctions in this library are categorized by their template heads. Thus, the source file of every function is placed based on such categorization. For example, to include a metafunction VeryFunny of template head template<typename...>, the include path shall be "conceptrodon/typelivore/very_funny.hpp". The following tables show the template heads of the metafunctions contained in each namespace/directory.

Namespace Template Head
Typelivore template<typename...>
Varybivore template<auto...>
Mouldivore template<template<typename...> class...>
Pagelivore template<template<auto...> class...>
Roadrivore template<template<template<typename...> class...> class...>
Raillivore template<template<template<auto...> class...> class...>

In addition to these namespaces, a couple more are introduced to contain functions whose parameters are of the form Vessel<Items...>.

Namespace Template Head of Vessel Primary Signature of Items
Cotanivore template<typename...> typename
Sequnivore template<auto...> auto
Warehivore template<template<typename...> class...> template<typename...>
Stockivore template<template<auto...> class...> template<auto...>

General-purpose functions that do not fit any previously mentioned namespaces are kept in Omennivore.

Namespace Content
Omennivore General-purpose functions

Finally, ordinary-function-related metafunctions are kept in the namespace Functivore. These metafunctions are created to inspect and simplify ordinary functions' signatures.

Namespace Content
Functivore ordinary-function-related metafunctions

Example

In the following example, we will make a metafunction called MakeMap that:

  1. Takes in a list of type arguments and extends it by void*, void**;
  2. Passes the extended list to MakeFunctionAlias to create a function signature that returns void;
  3. Instantiates std::function with the resulting signature;
  4. Instantiates std::map with size_t as the key_type and the instantiated std::function as the mapped_type.

We will use Mouldivore::Trip to compose the necessary metafunctions. Check out its documentation for more details.

namespace Conceptrodon {
namespace Mouldivore {

template<typename...Elements>
using MakeFunction = void(Elements...);

using SupposedMap = std::map<size_t, std::function<void(int, int*, void*, void**)>>;

template<typename...Elements>
using MakeMap = 
    Trip<BindBack<MakeFunction>::Mold<void*, void**>::Mold>
    ::Road<std::function>
    ::Road<BindFront<std::map>::Mold<size_t>::Mold>
    ::Commit
    ::Mold<Elements...>;

static_assert(std::same_as<MakeMap<int, int*>, SupposedMap>);

}}

We can use MakeMap again in the composition.

namespace Conceptrodon {
namespace Mouldivore {

using SupposedNestedMap 
= std::map<size_t, std::map<size_t, std::function<void(int, int*, void*, void**)>>>;

template<typename...Elements>
using MakeNestedMap =
    Trip<MakeMap>
    ::Road<BindFront<std::map>::Mold<size_t>::Mold>
    ::Commit
    ::Mold<Elements...>;

static_assert(std::same_as<MakeNestedMap<int, int*>, SupposedNestedMap>);

}}

Run this snippet on Godbolt.

Performance

Nothing intrinsic prevents Conceptrodon from utilizing a fast algorithm. While this library requires metafunctions to follow the same pattern, it is uninterested in what is happening within them. Many functions were tested against boost::mp11. If a function was slower than its counterpart, the implementation from boost::mp11 was used. Therefore, This library is generally faster than boost::mp11.

Limitation

This library heavily relies on 'concept expansion', which is only possible on Clang. Kris Jusiak explained the technique in this talk.

In the documentation of each metafunction, run the snippet in the implementation section on Godbolt to see if the metafunction works on GCC or MSVC.

Install

Conceptrodon is a header-only library. After downloading the code, move the directory ./conceptrodon to your project, then use it like your own headers.

You can also install Conceptrodon using CMake:

Windows

  1. Download the library.

  2. Open Developer Command Prompt that comes with Visual Studio. Redirect to the library folder. Generate a project buildsystem using CMake:

    cmake -S . -B "Where to build"
  3. Redirect to the build directory you specified after -B earlier. Run command:

    cmake --install . --prefix "Where to install"

After installation, add the install directory you specified after --prefix to variable CMAKE_PREFIX_PATH in your project's CMakeCache.txt. If the variable doesn't exist, you will need to add the following line to your CMakeCache.txt:

CMAKE_PREFIX_PATH:PATH=Install directory of the library

If CMAKE_PREFIX_PATH already exists, append the install directory to the values of the variable(note the added semicolon):

CMAKE_PREFIX_PATH:PATH=...; Install directory of the library

In the CMakeList.txt of your project, Add:

find_package(Conceptrodon REQUIRED CONFIG)
target_link_libraries(YourProject PRIVATE Conceptrodon::Facilities)

You are good to go.

Links

Documentation

About

A C++20 metaprogramming library focusing on metafunction composition.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • C++ 100.0%