All Projects → reacfen → uninttp

reacfen / uninttp

Licence: MIT License
A universal type for non-type template parameters for C++20 or later.

Programming Languages

C++
36643 projects - #6 most used programming language

Projects that are alternatives of or similar to uninttp

fixed string
C++17 string with fixed size
Stars: ✭ 64 (+300%)
Mutual labels:  constexpr, cpp20, cpp-templates
Compile Time Regular Expressions
A Compile time PCRE (almost) compatible regular expression matcher.
Stars: ✭ 2,144 (+13300%)
Mutual labels:  constexpr, compile-time, cpp20
opzioni
The wanna-be-simplest command line arguments library for C++
Stars: ✭ 29 (+81.25%)
Mutual labels:  constexpr, cpp20
flags17
A comparison of different options to implement binary flags in C++17
Stars: ✭ 16 (+0%)
Mutual labels:  constexpr
constexpr-java
build-time code-execution for java, a bit like constexpr in C++11
Stars: ✭ 58 (+262.5%)
Mutual labels:  constexpr
harmony
C++ Monadologie
Stars: ✭ 26 (+62.5%)
Mutual labels:  cpp20
static-string-cpp
Compile-time string manipulation library for modern C++
Stars: ✭ 34 (+112.5%)
Mutual labels:  compile-time
crynn
3D Game Engine Created In C++ & OpenGL
Stars: ✭ 45 (+181.25%)
Mutual labels:  cpp20
cpp-coroutine-job-system
Example Code for Cpp Coroutine Job System talk at CppCon 2020
Stars: ✭ 37 (+131.25%)
Mutual labels:  cpp20
sdsl-lite
Succinct Data Structure Library 3.0
Stars: ✭ 55 (+243.75%)
Mutual labels:  cpp20
HelvetaCS
Modern C++ CS:GO base
Stars: ✭ 41 (+156.25%)
Mutual labels:  cpp20
clc
No description or website provided.
Stars: ✭ 20 (+25%)
Mutual labels:  cpp20
asio-grpc
Asynchronous gRPC with Asio/unified executors
Stars: ✭ 100 (+525%)
Mutual labels:  cpp20
ghostwriter
Solutions for instrumenting application flow tracking API calls into an existing code base in a non-invasive way
Stars: ✭ 17 (+6.25%)
Mutual labels:  compile-time
cefal
(Concepts-enabled) Functional Abstraction Layer for C++
Stars: ✭ 52 (+225%)
Mutual labels:  cpp20
BinaryLove3
Simple C++ 20 Serialization Library that works out of the box with aggregate types!
Stars: ✭ 13 (-18.75%)
Mutual labels:  cpp20
StringPool
A performant and memory efficient storage for immutable strings with C++17. Supports all standard char types: char, wchar_t, char16_t, char32_t and C++20's char8_t.
Stars: ✭ 19 (+18.75%)
Mutual labels:  cpp20
agrpc
Async GRPC with C++20 coroutine support
Stars: ✭ 53 (+231.25%)
Mutual labels:  cpp20
malloy
A C++ library providing embeddable server & client components for both HTTP and WebSocket.
Stars: ✭ 29 (+81.25%)
Mutual labels:  cpp20
literal ipaddr
C++17 constexpr implementation of inet_addr / inet_aton / inet_pton
Stars: ✭ 19 (+18.75%)
Mutual labels:  constexpr

uninttp

A universal type for non-type template parameters for C++20 or later.

Installation:

uninttp (Universal Non-Type Template Parameters) is a header-only library. Just simply clone this repository and you are ready to go.

Once that is done, you can simply include the header(s) and start using uninttp in your project:

#include <uninttp/uni_auto.hpp>

uninttp also has a C++ module version, so if your compiler supports C++20 modules, you can do something like this instead of including the whole header file into your project:

import uninttp.uni_auto; // Improves compilation speed

Usage:

Using uninttp's uninttp::uni_auto is pretty straightforward and is synonymous to auto in most of the cases: Demo

#include <uninttp/uni_auto.hpp>

using namespace uninttp;

template <uni_auto Value>
constexpr auto add20() {
    return Value + 20;
}

int main() {
    static_assert(add20<20>() == 40); // OK
}

And if you thought "Can't I just use something like template <auto Value> instead?", then you'd be absolutely correct. One can safely replace uni_auto with auto, at least for this example.

However, a template parameter declared with uni_auto can do much more than a template parameter declared with auto in the sense that you can also pass string literals and constexpr-marked arrays (or arrays of static storage duration) through it: Demo

#include <uninttp/uni_auto.hpp>
#include <string_view>
#include <iostream>
#include <cstddef>
#include <array>

using namespace uninttp;

template <uni_auto Value, std::size_t X>
constexpr auto shift() {
    return Value + X;
}

template <uni_auto Array>
void print_array() {
    // Using range-based for loop
    for (const auto& elem : Array)
        std::cout << elem << " ";
    
    std::cout << std::endl;

    // Using iterators
    for (auto it = std::begin(Array); it != std::end(Array); ++it)
        std::cout << *it << " ";
    
    std::cout << std::endl;

    // Index-based for loop
    for (std::size_t i = 0; i < std::size(Array); i++)
        std::cout << Array[i] << " ";
    
    std::cout << std::endl;
}

static int arr2[] = {1, 2, 4, 8};

int main() {
    // Passing a string literal
    static_assert(std::string_view(shift<"foobar", 3>()) == "bar"); // OK

    // Passing an array marked as 'constexpr'
    constexpr int arr1[] = { 1, 8, 9, 20 };
    print_array<arr1>();                                            // 1 8 9 20

    // Passing an array of static storage duration
    print_array<arr2>();                                            // 1 2 4 8

    // Passing an 'std::array' object
    print_array<std::array { 1, 4, 6, 9 }>();                       // 1 4 6 9
}

You can also use it with parameter packs, obviously: Demo

#include <uninttp/uni_auto.hpp>
#include <iostream>

using namespace uninttp;

template <uni_auto ...Values>
void print() {
    ((std::cout << Values << " "), ...) << std::endl;
}

int main() {
    print<1, 3.14159, 6.3f, "foo">(); // 1 3.14159 6.3 foo
}

You can also enforce a type by adding a constraint: Demo

#include <uninttp/uni_auto.hpp>
#include <concepts>

using namespace uninttp;

template <uni_auto Value>
// 'uni_auto_simplify_t<Value>' gives you the simplified type for type-checking convenience
requires std::same_as<uni_auto_simplify_t<Value>, const char*>
void only_accepts_strings() {}

int main() {
    only_accepts_strings<"foobar">(); // OK
    // only_accepts_strings<123>();   // Error! Constraint not satisfied!
}

Note: One can also "exploit" the above combination of constraints and uni_auto to achieve a sort of "function overloading through template parameters" mechanism: Demo

#include <uninttp/uni_auto.hpp>
#include <concepts>
#include <iostream>

using namespace uninttp;

template <uni_auto Value>
requires std::same_as<uni_auto_simplify_t<Value>, const char*>
void do_something() {
    std::cout << "A string was passed" << std::endl;
}
template <uni_auto Value>
requires std::same_as<uni_auto_simplify_t<Value>, int>
void do_something() {
    std::cout << "An integer was passed" << std::endl;
}

int main() {
    do_something<"foobar">(); // A string was passed
    do_something<123>();      // An integer was passed
    // do_something<12.3>();  // Error!
}

Unsurprisingly, one can pass trivial structs through uni_auto as well: Demo

#include <uninttp/uni_auto.hpp>

using namespace uninttp;

struct X {
    int val = 6;
};

struct Y {
    int val = 7;
};

template <uni_auto A, uni_auto B>
constexpr auto mul() {
    return A.val * B.val;
}

int main() {
    static_assert(mul<X{}, Y{}>() == 42); // OK
}

You can pass lambdas and functors through uni_auto as well: Demo

#include <uninttp/uni_auto.hpp>

using namespace uninttp;

template <uni_auto F>
constexpr auto call() {
    return F();
}

struct Funct {
    constexpr auto operator()() const {
        return 86;
    }
};

int main() {
    static_assert(call<[] { return 69; }>() == 69); // OK
    static_assert(call<Funct{}>() == 86);           // OK
}

And it doesn't end there! uni_auto can also work with pointers to objects:

Example using a pointer to an object: Demo

#include <uninttp/uni_auto.hpp>
#include <iostream>

using namespace uninttp;

template <uni_auto p>
void print_pointer() {
    std::cout << p << std::endl;
}

int y = 3;

int main() {
    static constexpr int x = 2;
    print_pointer<&x>(); // Prints the location of 'x' in memory
    print_pointer<&y>(); // Prints the location of 'y' in memory
}

Example using function pointers (This currently does NOT compile on GCC.): Demo

#include <uninttp/uni_auto.hpp>
#include <iostream>

using namespace uninttp;

constexpr auto some_fun() {
    return 42;
}

template <uni_auto Func>
constexpr auto call_fun() {
    return Func();
}

int main() {
    static_assert(call_fun<some_fun>() == 42); // OK
}

Example using a pointer to a member function: Demo

#include <uninttp/uni_auto.hpp>
#include <iostream>

using namespace uninttp;

struct some_class {
    void some_member(int& p) const {
        p = 2;
    }
};

template <uni_auto Value>
void call_member(const some_class& x, int& y) {
    (x->*Value)(y); // Alternatively, you can write: '(x.*uni_auto_v<Value>())(y);'
}

int main() {
    some_class x;
    int y;

    call_member<&some_class::some_member>(x, y);

    std::cout << y << std::endl;
}

All the examples shown have used function templates to demonstrate the capability of uni_auto. However, it can readily be used in any context.

Test suite:

An exhaustive test on uninttp's uninttp::uni_auto has been done to ensure that it consistently works for almost every non-type template argument allowed.

Note: Some of the features portrayed in the test suite might not work on all compilers as C++20 support, as of writing this, is still in an experimental stage on most compilers.

The test suite can be found here.

(P.S.: For reference, one can look up this link.)

Cheat sheet:

Description
uninttp::uni_auto_t<uni_auto Value> Gives the type of the underlying value held by the uni_auto object passed to it.
uninttp::uni_auto_simplify_t<uni_auto Value> Gives the decayed type of the value held by the uni_auto object.
If the uni_auto object holds an array, it decays it into a pointer and returns the pointer as the type.
This feature is often useful for doing compile-time type-checking, SFINAE and for defining certain constraints on the types held by the uni_auto object.
uninttp::uni_auto_v<uni_auto Value>() Effectively extracts the underlying value held by the uni_auto object passed to it.
uninttp::uni_auto_simplify_v<uni_auto Value>() Converts the underlying value of the uni_auto object into its simplest form. If the value held is an array, it converts it into a pointer and returns that, otherwise it does the exact same thing as uni_auto_v.
uninttp::propagate</* uni_auto / const auto& */ Value>() Constructs a `uni_auto` object using `Value`. Usually redundant except for one special case. (See (4) of Restrictions below.)

Restrictions:

  1. The datatype of the value held by a uni_auto object cannot be fetched using decltype(X) as is done with auto-template parameters. Instead, one would have to do something like this instead: Demo

    #include <uninttp/uni_auto.hpp>
    #include <type_traits>
    
    using namespace uninttp;
    
    template <uni_auto X = 1.89>
    void fun() {
        // This doesn't work for obvious reasons
        // static_assert(std::same_as<decltype(X), double>);                                            // Error
    
        // Using 'uni_auto_t':
        static_assert(std::is_same_v<uni_auto_t<X>, double>);                                           // OK
    
        // Using 'uni_auto_v' and then using 'decltype()' and removing the const specifier from the type returned:
        static_assert(std::is_same_v<std::remove_const_t<decltype(uni_auto_v<X>())>, double>);          // OK
    
        // Using 'uni_auto_simplify_t':
        static_assert(std::is_same_v<uni_auto_simplify_t<X>, double>);                                  // OK
    
        // Using 'uni_auto_simplify_v' and then using 'decltype()' and removing the const specifier from the type returned:
        static_assert(std::is_same_v<std::remove_const_t<decltype(uni_auto_simplify_v<X>())>, double>); // OK
    }
    
    int main() {
        fun<>();
    }
  2. There may be some cases where conversion operator of the uni_auto object doesn't get invoked. In such a scenario, one would need to explicitly notify the compiler to extract the value out of the uni_auto object: Demo

    #include <uninttp/uni_auto.hpp>
    #include <type_traits>
    
    using namespace uninttp;
    
    template <uni_auto X = 42>
    void fun() {
        // Using an explicit conversion statement:
        constexpr int answer1 = X;
    
        // Using 'uni_auto_v':
        constexpr auto answer2 = uni_auto_v<X>();
    
        // Using 'uni_auto_simplify_v':
        constexpr auto answer3 = uni_auto_simplify_v<X>();
    
        static_assert(std::is_same_v<std::remove_const_t<decltype(answer1)>, int>); // OK
        static_assert(std::is_same_v<std::remove_const_t<decltype(answer2)>, int>); // OK
        static_assert(std::is_same_v<std::remove_const_t<decltype(answer3)>, int>); // OK
    }
    
    int main() {
        fun<>();
    }
  3. This is more or less an extension to the (2) restriction.

    Using lvalue references of class types with uninttp::uni_auto is a little tricky as the dot operator does not function as expected. Instead, one would have to do something like this: Demo

    #include <uninttp/uni_auto.hpp>
    #include <iostream>
    #include <cassert>
    
    using namespace uninttp;
    
    struct some_class {
        int p = 0;
        int& operator=(const int& rhs) {
            return p = rhs;
        }
    };
    
    template <uni_auto X>
    void fun() {
        X = 2; // Assignment operator works as expected
    
        // If you want to access 'p' directly, you would have to call 'uni_auto_v' explicitly:
    
        // auto a = X.p;                   // This will NOT work since the C++ Standard does not allow
                                           // overloading the dot operator (yet)
    
        const auto c = uni_auto_v<X>().p; // OK
    
        std::cout << c << std::endl;      // 2
    }
    
    some_class some_obj;
    
    int main() {
        fun<some_obj>();
    }
  4. Currently, uni_auto tends to break when passed constants with addresses of static-storage duration. This is due to the fact that both constexpr and constants of static-storage duration bind to a const reference, so it is impossible to determine which one was passed by the user. In such scenarios, the propagate<Value>() function ought to be used to do a manual conversion to a uni_auto object beforehand: Demo

#include <uninttp/uni_auto.hpp>
#include <iostream>

using namespace uninttp;

template <uni_auto X>
void p() {
    std::cout << X << std::endl;
}

int main() {
    static constexpr auto str = "String with static storage duration";

    // p<str>();           // Doesn't work, sadly
    
    p<propagate<str>()>(); // OK!
}

Playground:

If you'd like to play around with uni_auto yourself, here you go!

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].