Skip to content

Instantly share code, notes, and snippets.

@TerensTare
Last active October 10, 2021 18:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerensTare/f8ca49bf2db767129d3fc229e5471a84 to your computer and use it in GitHub Desktop.
Save TerensTare/f8ca49bf2db767129d3fc229e5471a84 to your computer and use it in GitHub Desktop.
Lazily-deduced template params in C++

This gist contains examples on how to let C++ deduce explicit template parameters.

Consider std::bit_cast, introduced in C++20. You can use it as a replacement to std::memcpy that can work in compile time as well. An example usage is as follows:

    struct rgba final
    {
        std::uint8_t r;
        std::uint8_t g;
        std::uint8_t b;
        std::uint8_t a = 255;
    };

    constexpr auto color = std::bit_cast<rgba>(0xffffffff);
                                      // ^ notice how we specified the casted type as a template parameter.

With the help of the techniques used in this gist, you can instead write:

       // v notice that the type is specified here
constexpr rgba color = tnt::bit_cast(0xffffffff);
                    // ^ we aren't using namespace std anymore

This mimics Rust's lazy type deduction (to an extent) and aims to make the code more readable. Please notice that the return type of tnt::bit_cast stores a pointer to the passed parameter, so the following code exposes UB:

void f(std::uint32_t u) { std::cout << x; }

constexpr auto ucolor = tnt::bit_cast(rgba{255, 255, 255});
       // ^ notice the `auto` here
f(ucolor);

The type of ucolor is NOT std::uint32_t, but it's rather a type that stores a pointer to the passed rgba value and can be converted to std::uint32_t (where the actual std::bit_cast happens). Given the passed parameter is a temporary, the pointer will be dangling and thus reading it on the conversion operator is UB. For this purpose, tnt::lazy_bit_cast is introduced, the return type of which copies the passed parameter (std::bit_cast requires it to be trivially copyable, so it should be a cheap operation). This way the stored value will never dangle and the following code will not expose any UB.

void f(std::uint32_t u) { std::cout << x; }

constexpr auto ucolor = tnt::lazy_bit_cast(rgba{255, 255, 255});
f(ucolor); // works fine, no dangling pointers or whatever

Furthermore, an implementation of a Python-like input function is provided, with more functions and use cases to come.

The techniques used are explained in depth (better than I could) on TartanLLama's blog post.

#include <bit>
#include <memory>
namespace tnt
{
namespace detail
{
template <typename From>
requires std::is_trivially_copyable_v<From>
struct bit_cast_result final
{
constexpr bit_cast_result(From const &from) noexcept
: from{std::addressof(from)} {}
template <typename To>
requires std::is_trivially_copyable_v<To>
&& (sizeof(From) == sizeof(To))
constexpr operator To() const noexcept
{
return std::bit_cast<To>(*from);
}
private:
const From *from;
};
}
template <typename From>
requires std::is_trivially_copyable_v<From>
constexpr auto bit_cast(From const &from) noexcept
{
return detail::bit_cast_result{from};
}
}
using u8 = std::uint8_t;
struct rgba final
{
u8 r;
u8 g;
u8 b;
u8 a = 255;
};
int main()
{
constexpr rgba color = tnt::bit_cast(0xffffffff);
return color.r == 255 && color.g == 255
&& color.b == 255 && color.a == 255;
}
#include <bit>
#include <concepts>
#include <iostream>
#include <sstream>
#include <string_view>
namespace detail
{
template <typename...>
inline static constexpr bool always_false = false;
}
struct ubiq final
{
template <typename T>
requires requires (std::istringstream &stream, T &t) {
stream >> t;
}
operator T() const
{
if constexpr (std::default_initializable<T>)
{
T t;
std::istringstream{val} >> t;
return t;
}
else if constexpr (std::is_trivially_copyable_v<T>)
{
std::byte storage[sizeof(T)];
std::fill_n(storage, sizeof(T), std::byte{});
T t = std::bit_cast<T>(storage);
std::istringstream{val} >> t;
return t;
}
else
{
static_assert(detail::always_false<T>, "input requires the result type to be at least default initializable or trivially copyable!!");
}
}
operator std::string() const
{
return val;
}
operator std::string &&() &&
{
return std::move(val);
}
friend std::istream &operator >>(std::istream &stream, ubiq &u)
{
stream >> u.val;
return stream;
}
friend std::ostream &operator <<(std::ostream &stream, ubiq const& ub)
{
stream << ub.val;
return stream;
}
private:
ubiq() noexcept = default;
std::string val = "";
friend ubiq input(std::string_view, const char);
};
ubiq input(std::string_view message, const char delim = '\n')
{
std::cout << message << delim;
ubiq u;
std::cin >> u;
return u;
}
int main()
{
int test = input("Hello world!");
std::cout << test << '\n';
std::cout << input("Hello again");
}
#include <bit>
namespace tnt
{
namespace detail
{
template <typename From>
requires std::is_trivially_copyable_v<From>
struct lazy_bit_cast_result final
{
constexpr lazy_bit_cast_result(From const &from) noexcept
: from{from} {}
template <typename To>
requires std::is_trivially_copyable_v<To>
&& (sizeof(From) == sizeof(To))
constexpr operator To() const noexcept
{
return std::bit_cast<To>(from);
}
private:
From from;
};
}
template <typename From>
requires std::is_trivially_copyable_v<From>
constexpr auto lazy_bit_cast(From const &from) noexcept
{
return detail::lazy_bit_cast_result{from};
}
}
#include <iostream>
using u8 = std::uint8_t;
struct rgba final
{
u8 r;
u8 g;
u8 b;
u8 a = 255;
};
void print(std::uint32_t x) { std::cout << x; }
int main()
{
constexpr auto color = tnt::lazy_bit_cast(rgba{255, 255, 255});
print(color);
}
MIT License
Copyright (c) 2021 Terens Tare
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment