[llvm-dev] RFC: Strong typedef for LLVM
David Greene via llvm-dev
llvm-dev at lists.llvm.org
Tue Aug 13 12:41:44 PDT 2019
For anyone interested, I've posted a patch as D66148.
https://reviews.llvm.org/D66148
-David
"Doerfert, Johannes" <jdoerfert at anl.gov> writes:
> +1, I like this a lot.
>
> Recently, I started to use two element `enum class`es to force people (mostly me)
> to spell out what true/false actually means in a certain context. The solution proposed
> here comes with more boilerplate but once available it is nicer (I think) and for sure
> more generic.
>
> ________________________________________
> From: llvm-dev <llvm-dev-bounces at lists.llvm.org> on behalf of David Greene via llvm-dev <llvm-dev at lists.llvm.org>
> Sent: Thursday, August 1, 2019 10:57
> To: llvm-dev at lists.llvm.org
> Subject: [llvm-dev] RFC: Strong typedef for LLVM
>
> Lately I've been using some utilities to increase the number of logic
> errors caught at compile time. I thought they might be useful to the
> LLVM project. I'd appreciate feedback on the below proposal. Would the
> community find these useful?
>
> -David
>
> RFC: Strong typedef utilities for LLVM
> --------------------------------------
>
> Abstract
> --------
> This proposal describes a set of utility classes for increasing type
> safety within the LLVM project codebase. It introduces a general
> "strong typedef" template along with more specialized templates for
> Boolean values, accumulators and counters.
>
> The interface and implementation are inspired by Jonathan Mueller's
> post on strong typedef [1].
>
> The Problem
> -----------
> As noted on a side-thread of the variable naming convention change,
> LLVM has a number of places where function parameter semantics are
> unclear at the point of use [2]. To compensate, much code exists
> in this style:
>
> foo(arg1, /* SomeFlagName = */true);
>
> Functions with, for example, two boolean parameters are often called
> as:
>
> foo(arg1, /* Flag1Name = */true, /* Flag2Name =*/false);
>
> Unfortunately, it is easy to mix this up unintentionally:
>
> foo(arg1, /* Flag1Name = */false, /* Flag2Name =*/true);
>
> The above may or may not be a bug. The intent is unclear from the
> context.
>
> Things are even worse without comments:
>
> foo(arg1, true, false); // What does this mean?
>
> bool SomeFlag = true;
> bool AnotherFlag = false;
>
> foo(arg1, AnotherFlag, SomeFlag); // Is this correct?
>
> bool DoThing = bar();
> bool EnableFeature = baz();
>
> foo(arg1, EnableFeature, DoThing); // Still not clear.
>
> bar(size, alignment); // Did I get that in the right order?
> // Is size in bits or bytes?
>
> A Possible Solution
> -------------------
> It would be nice if the Flag1Name and Flag2Name comments had semantic
> meaning, such that they could be checked for correctness at compile
> time. It would be even better if such semantic information was
> *required* at the point of use. One could construct special classes
> to make it so:
>
> class Flag1Value {
> bool Value;
>
> public:
> Flag1Value : Value(false) {}
> explicit Flag1Value(bool V) : Value(V) {}
>
> explicit operator bool() { return Value; }
> };
>
> class Flag2Value {
> bool Value;
>
> public:
> Flag2Value : Value(false) {}
> explicit Flag2Value(bool V) : Value(V) {}
>
> explicit operator bool() { return Value; }
> };
>
> void foo(int arg, Flag1Value F1, Flag2Value F2);
>
> foo(arg1, Flag1Value(true), Flag2Value(false));
> foo(arg1, Flag2Value(true), Flag1Value(false)); // Won't compile.
>
> Of course, defining individual classes for every kind of flag does not
> scale. However, a template class can make it workable:
>
> template<typename Tag, typename T>
> class StrongTypedef {
> public:
> using BaseType = T;
>
> private:
> BaseType Value;
>
> public:
> constexpr StrongTypedef() : Value() {}
> constexpr StrongTypedef(const StrongTypedef<Tag, T> &) = default;
> explicit constexpr StrongTypedef(const BaseType &V) : Value(V) {}
> explicit StrongTypedef(BaseType &&V)
> noexcept(std::is_nothrow_move_constructible<BaseType>::value) :
> Value(std::move(V)) {}
>
> public:
> explicit operator BaseType&() noexcept {
> return Value;
> }
>
> explicit operator const BaseType&() const noexcept {
> return Value;
> }
>
> friend void swap(StrongTypedef &A, StrongTypedef &B) noexcept {
> using std::swap;
> swap(static_cast<BaseType&>(A), static_cast<BaseType&>(B));
> }
> };
>
> class Flag1Value : public StrongTypedef<Flag1Value, bool> {
> public:
> using StrongTypedef::StrongTypedef;
> };
>
> class Flag2Value : public StrongTypedef<Flag2Value, bool> {
> public:
> using StrongTypedef::StrongTypedef;
> };
>
> Note that Tag does not have to be a derived class and StrongTypedef
> does not have to be used in a CRTP manner, though it is often
> convenient to do so.
>
> The StrongTypedef template doesn't provide any operations outside of
> explicit conversion to/from the base type. Mixin classes can imbue it
> with new interfaces:
>
> // Mixin to add equality and inequality comparison.
> template<typename ST>
> struct HasEqualityCompare {
> friend bool operator==(const ST &LHS, const ST &RHS) {
> using BaseType = typename ST::BaseType;
>
> return (static_cast<const BaseType &>(LHS) ==
> static_cast<const BaseType &>(RHS));
> }
>
> friend bool operator!=(const ST &LHS, const ST &RHS) {
> return !(LHS == RHS);
> }
> };
>
> class Flag1Value : public StrongTypedef<Flag1Value, bool>,
> public HasEqualityCompare<Flag1Value> {
> public:
> using StrongTypedef::StrongTypedef;
> };
>
> Flag1Value True(true);
> Flag1Value False(false);
>
> assert(True != False);
> assert(True == True);
>
> StrongTypedef can be used to create rudimentary units types:
>
> class Bits : StrongTypedef<Bits, int64_t> {
> public:
> using StrongTypedef::StrongTypedef;
> };
>
> class Bytes : StrongTypedef<Bytes, int64_t> {
> public:
> using StrongTypedef::StrongTypedef;
> };
>
> Bits size(32);
> Bytes alignment(4);
>
> void bar(Bytes size, Bytes alignment);
>
> bar(size, alignment); // Won't compile
>
> Boolean parameters are so common that it's convenient to have a
> special helper for them:
>
> template<typename Tag>
> class NamedBoolean : public StrongTypedef<Tag, bool>,
> public HasEqualityCompare<NamedBoolean<Tag>>,
> public HasLogicalConjunction<NamedBoolean<Tag>>,
> public HasLogicalDisjunction<NamedBoolean<Tag>> {
> public:
> using StrongTypedef<Tag, bool>::StrongTypedef;
> };
>
> class Flag1Value : public NamedBoolean<Flag1Value> {
> public:
> using NamedBoolean::NamedBoolean;
> };
>
> It can also be useful to have utilities for strong typedefed counters
> and accumulators:
>
> template<typename Tag, typename Int = int64_t>
> class NamedCounter : public StrongTypedef<Tag, Int>,
> public HasEqualityCompare<NamedCounter<Tag>>,
> public HasIncrement<NamedCounter<Tag>> { // ++
> public:
> using StrongTypedef<Tag, int64_t>::StrongTypedef;
> };
>
> template<typename Tag, typename Int = int64_t>
> class NamedAccumulator : public StrongTypedef<Tag, Int>,
> public HasEqualityCompare<NamedAccumulator<Tag>>,
> public HasAccumulate<NamedAccumulator<Tag>> { // +=
> public:
> using StrongTypedef<Tag, int64_t>::StrongTypedef;
> };
>
> Proposal
> --------
> This RFC proposes nothing more than introduction of these templates
> under include/llvm/ADT to make them available to developers who want
> extra safety in their code. It specifically does not propose rewriting
> existing uses of flags, counters, accumulators or any other values to
> use these utilities, nor does it mandate their use in new code. Such
> changes may be desirable but should happen incrementally over time.
>
> References
> ----------
> [1] https://foonathan.net/blog/2016/10/19/strong-typedefs.html[2] http://llvm.1065342.n5.nabble.com/llvm-dev-RFC-changing-variable-naming-rules-in-LLVM-code_______________________________________________
> LLVM Developers mailing list
> llvm-dev at lists.llvm.org
> https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev
More information about the llvm-dev
mailing list